├── .gitignore ├── .travis.yml ├── INSTALL.md ├── README.md ├── img └── img1.jpg ├── include └── README ├── lib ├── BG_RF95 │ ├── BG_RF95.cpp │ └── BG_RF95.h └── README ├── platformio.ini └── src ├── TTGO_T-Beam_LoRa_APRS.ino └── TTGO_T-Beam_LoRa_APRS_config.h /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .pioenvs 3 | .piolibdeps 4 | .clang_complete 5 | .gcc-flags.json 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 |

Installation Guide using PlatformIO

2 |
3 | 1. Clone all files of the respository to your local working directory
4 | 2. Install the missing libraries
5 | There are two possibilities - either using the Library Manager of PlatformIO or the command line tool:
6 |

1. Built-In Liabrary Manager

7 | Press the PlatformIO HOME Button to enter the Home Screen and there the Libraries Button to add missing libraries:
8 |
9 | Search and install the following libaries:
10 | 21 |
22 |

2. Command Line Tool

23 | use the following commands
24 | platformio lib install "RadioHead"
25 | platformio lib install "TinyGPSPlus"
26 | platformio lib install "DHT sensor library for ESPx"
27 | platformio lib install "Adafruit SSD1306"
28 | platformio lib install "Adafruit GFX Library"
29 | platformio lib install "AXP202X_Library"
30 | platformio lib install "Adafruit Unified Sensor"
31 | platformio lib install "OneWire"
32 | platformio lib install "DallasTemperature"
33 |
34 | Check that the platformio.ini is available as it holds the board type for PlatformIO.
35 | After pressing the check mark the code will be compiled, after pressing the arrow it will be compiled and uploaded to a connected TTGO.
36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | First of all I want to thank OE1ACM Bernd for his approval to use his code for my experiments.
2 | He is the author of BG_RF95!
3 |
4 | If you want to discuss with other LoRa APRS interested persons join the growing community at the LoRa-APRS Telegram group
5 |
6 | USER GUIDE of TTGO T-Beam LoRa APRS V1.2:
7 | Attention: a new HW Version is available - if you use the old version uncomment "// #define T_BEAM_V0_7" and comment out "#define T_BEAM_V1_0".You can recognize the new Rev1.0-Boards at their three push buttons instead of two at the older boards - both versions are now supported.
8 | As the new board only has two LEDs for the GPS and battery charger I've moved the TX Led to PIN 4 (GPIO 4) - please use a LED with reasonable resistor of 470R.
9 |
10 | You can now do an immediate TX beacon if you press the key for 2secs - use this for testing purposes.
11 |
12 | Callsign setting:
13 | Now two possibilities are implemented:
14 | 1st) enter your callsign in the file TTGO_T-Beam_LoRa_APRS_config.h and replace the OE3CJB-11 with your data with 6 characters plus SSID-
15 | in case of shorter callsign use "SPACES" and extend up to total length of 6 characters
16 | 2nd) if you leave the config-file unchanged, you will run into a simple setup routine at the very first boot and you will be asked to enter your callsign and SSID(s).
17 | Once the callsign is programmed you can reenter the programming mode by pressing the BUTTON for 5secs while switching on the tracker.
18 | When you uncomment this line // #define DONT_USE_FLASH_MEMORY in TTGO_T-Beam_LoRa_APRS_config.h then the FW will always use the values from the config-file
19 |
20 | The MODE of the tracker can now be changed by pressing the button 10secs!
21 | This can be done now without connected display but with a mounted TX LED.
22 | The modes are
23 | TRACKER ... LED blinks 1x - normal APRS tracker
24 | WX&TRACKER ... LED blinks 2x - alternate transmission of normal position packet and WX packet (if DHT22 is mounted)
25 | WX-MOVE ... LED blinks 3x - only WX packets are sent but with position from GPS
26 | WX-FIXED ... LED blinks 4x - only WX packets are transmitted but with fixed position given in Header-File
27 |
28 | The fixed position is used for a fixed weather station, e.g. without GPS signal.
29 |
30 | Possible symbols are
31 | Weather Station (1x blink), Car (2x blink), Runner (3x blink), Bicyle (4x blink), Motorcycle (5x blink)
32 | The symbol can now be changed without attached display - during normal operation press the key for 3secs and you will enter the setup routine. The first value to set is now the symbol and the currently selected symbol is represented by blinks of the TX LED. Once the one needed blinks, just press the key for short moment. After that you will be asked if you want to continue the setup routine (which only makes sense with a connected display) or if you want to stop and so the new symbol will be stored in the NVS memory.
33 | 2x TX LED blinks represent "yes" to leave the setup - press here the key to leave the setup - please do so if you don't have a display attached.
34 | 1x TX LED blinks represent "no" to continue with the setup - press here the key to continue the setup - please do so if you have a display attached.
35 |
36 | Temperature Sensor:
37 | for DHT22 I used the library from https://github.com/beegee-tokyo/DHTesp, as the standard library gives to many wrong readings
38 | Now the DS18B20 is supported as well - uncomment line 37: // #define DS18B20 // use this if you use DS18B20, default ist DHT22
39 | Now the BME280 is supported as well - uncomment line 38: // #define USE_BME280 // use this if you use BME280, default ist DHT22
40 |
41 | show RX packets
42 | by uncommenting // #define SHOW_RX_PACKET the tracker shows received LoRa APRS packets in raw format for the time in milliseconds defined in SHOW_RX_TIME - both in ...config.h
43 |
44 | new features:
45 | - Trackermode setable via config file 46 | - BME280 sensor implemented (Temp und Hum only) 47 | - compressed packets in tracker mode 48 | - symbol RV added 49 | - show RX packets 50 | - DS18B20 support (setable in config.h) 51 | - GPS switched off in WX_FIXED mode (only available with boards with HW-Version >=V1.0) 52 | - immediate TX with short key press 53 | - course changes of >30° will cause a TX beacon 54 | - code optimized and cleaned 55 | - preset of callsign and SSID in file TTGO_T-Beam_LoRa_APRS_config.h --- this is the only you should change - if you are not familiar with programming ;-)
56 | - corrected format of speed, course and height to be shown correctly in aprs.fi and aprsdirect.com 57 | - Smart Beaconing - the maximum period can be set in the config-file - the minimum period is limited to 60sec, the calculation is based on speeds between 0 and 50 km/h, the default is smart beaconing is off with a minimium period setting of 60secs
58 | - usage of shorter callsigns is now also possible - fill up with SPACES up to 6 characters please
59 | - support of new power management chip AXP192
60 | -------------------------------------------------------------------------------- /img/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oe3cjb/TTGO-T-Beam-LoRa-APRS/b886643cbeb5a541250a322625820846db2b8069/img/img1.jpg -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/BG_RF95/BG_RF95.cpp: -------------------------------------------------------------------------------- 1 | // BG_RF95.cpp 2 | // 3 | // Copyright (C) 2011 Mike McCauley 4 | // $Id: BG_RF95.cpp,v 1.11 2016/04/04 01:40:12 mikem Exp $ 5 | 6 | #include 7 | 8 | byte _lastSNR = 0; 9 | 10 | // Interrupt vectors for the 3 Arduino interrupt pins 11 | // Each interrupt can be handled by a different instance of BG_RF95, allowing you to have 12 | // 2 or more LORAs per Arduino 13 | BG_RF95* BG_RF95::_deviceForInterrupt[BG_RF95_NUM_INTERRUPTS] = {0, 0, 0}; 14 | uint8_t BG_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next device 15 | 16 | // These are indexed by the values of ModemConfigChoice 17 | // Stored in flash (program) memory to save SRAM 18 | PROGMEM static const BG_RF95::ModemConfig MODEM_CONFIG_TABLE[] = 19 | { 20 | // 1d, 1e, 26 21 | { 0x72, 0x74, 0x00}, // Bw125Cr45Sf128 (the chip default) 22 | { 0x92, 0x74, 0x00}, // Bw500Cr45Sf128 23 | { 0x48, 0x94, 0x00}, // Bw31_25Cr48Sf512 24 | { 0x78, 0xc4, 0x00}, // Bw125Cr48Sf4096 25 | { 0x72, 0xc7, 0x8}, // BG 125 cr45 sf12 26 | }; 27 | 28 | BG_RF95::BG_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI& spi) 29 | : 30 | RHSPIDriver(slaveSelectPin, spi), 31 | _rxBufValid(0) 32 | { 33 | _interruptPin = interruptPin; 34 | _myInterruptIndex = 0xff; // Not allocated yet 35 | } 36 | 37 | bool BG_RF95::init() 38 | { 39 | if (!RHSPIDriver::init()) 40 | return false; 41 | //Serial.println("RHSPIDriver::init completed"); 42 | // Determine the interrupt number that corresponds to the interruptPin 43 | int interruptNumber = digitalPinToInterrupt(_interruptPin); 44 | if (interruptNumber == NOT_AN_INTERRUPT) 45 | return false; 46 | #ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER 47 | interruptNumber = _interruptPin; 48 | #endif 49 | //Serial.println("Attach Interrupt completed"); 50 | 51 | // No way to check the device type :-( 52 | 53 | // Set sleep mode, so we can also set LORA mode: 54 | spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_SLEEP | BG_RF95_LONG_RANGE_MODE); 55 | delay(10); // Wait for sleep mode to take over from say, CAD 56 | // Check we are in sleep mode, with LORA set 57 | if (spiRead(BG_RF95_REG_01_OP_MODE) != (BG_RF95_MODE_SLEEP | BG_RF95_LONG_RANGE_MODE)) 58 | { 59 | //Serial.println(spiRead(BG_RF95_REG_01_OP_MODE), HEX); 60 | return false; // No device present? 61 | } 62 | 63 | // Add by Adrien van den Bossche for Teensy 64 | // ARM M4 requires the below. else pin interrupt doesn't work properly. 65 | // On all other platforms, its innocuous, belt and braces 66 | pinMode(_interruptPin, INPUT); 67 | 68 | // Set up interrupt handler 69 | // Since there are a limited number of interrupt glue functions isr*() available, 70 | // we can only support a limited number of devices simultaneously 71 | // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the 72 | // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping 73 | // yourself based on knwledge of what Arduino board you are running on. 74 | if (_myInterruptIndex == 0xff) 75 | { 76 | // First run, no interrupt allocated yet 77 | if (_interruptCount <= BG_RF95_NUM_INTERRUPTS) 78 | _myInterruptIndex = _interruptCount++; 79 | else 80 | return false; // Too many devices, not enough interrupt vectors 81 | } 82 | _deviceForInterrupt[_myInterruptIndex] = this; 83 | if (_myInterruptIndex == 0) 84 | attachInterrupt(interruptNumber, isr0, RISING); 85 | else if (_myInterruptIndex == 1) 86 | attachInterrupt(interruptNumber, isr1, RISING); 87 | else if (_myInterruptIndex == 2) 88 | attachInterrupt(interruptNumber, isr2, RISING); 89 | else 90 | { 91 | //Serial.println("Interrupt vector too many vectors"); 92 | return false; // Too many devices, not enough interrupt vectors 93 | } 94 | 95 | // Set up FIFO 96 | // We configure so that we can use the entire 256 byte FIFO for either receive 97 | // or transmit, but not both at the same time 98 | spiWrite(BG_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0); 99 | spiWrite(BG_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0); 100 | 101 | // Packet format is preamble + explicit-header + payload + crc 102 | // Explicit Header Mode 103 | // payload is TO + FROM + ID + FLAGS + message data 104 | // RX mode is implmented with RXCONTINUOUS 105 | // max message data length is 255 - 4 = 251 octets 106 | 107 | setModeIdle(); 108 | 109 | // Set up default configuration 110 | // No Sync Words in LORA mode. 111 | setModemConfig(Bw125Cr45Sf128); // Radio default 112 | // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? 113 | setPreambleLength(8); // Default is 8 114 | // An innocuous ISM frequency, same as RF22's 115 | setFrequency(433.800); 116 | // Lowish power 117 | setTxPower(20); 118 | 119 | return true; 120 | } 121 | 122 | // C++ level interrupt handler for this instance 123 | // LORA is unusual in that it has several interrupt lines, and not a single, combined one. 124 | // On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly 125 | // connnected to the processor. 126 | // We use this to get RxDone and TxDone interrupts 127 | void BG_RF95::handleInterrupt() 128 | { 129 | // Read the interrupt register 130 | //Serial.println("HandleInterrupt"); 131 | uint8_t irq_flags = spiRead(BG_RF95_REG_12_IRQ_FLAGS); 132 | if (_mode == RHModeRx && irq_flags & (BG_RF95_RX_TIMEOUT | BG_RF95_PAYLOAD_CRC_ERROR)) 133 | { 134 | _rxBad++; 135 | } 136 | else if (_mode == RHModeRx && irq_flags & BG_RF95_RX_DONE) 137 | { 138 | // Have received a packet 139 | uint8_t len = spiRead(BG_RF95_REG_13_RX_NB_BYTES); 140 | 141 | // Reset the fifo read ptr to the beginning of the packet 142 | spiWrite(BG_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(BG_RF95_REG_10_FIFO_RX_CURRENT_ADDR)); 143 | spiBurstRead(BG_RF95_REG_00_FIFO, _buf, len); 144 | _bufLen = len; 145 | spiWrite(BG_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags 146 | 147 | // Remember the RSSI of this packet 148 | // this is according to the doc, but is it really correct? 149 | // weakest receiveable signals are reported RSSI at about -66 150 | _lastRssi = spiRead(BG_RF95_REG_1A_PKT_RSSI_VALUE) - 137; 151 | 152 | _lastSNR = spiRead(BG_RF95_REG_19_PKT_SNR_VALUE); 153 | 154 | // We have received a message. 155 | validateRxBuf(); 156 | if (_rxBufValid) 157 | setModeIdle(); // Got one 158 | } 159 | else if (_mode == RHModeTx && irq_flags & BG_RF95_TX_DONE) 160 | { 161 | _txGood++; 162 | setModeIdle(); 163 | } 164 | 165 | spiWrite(BG_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags 166 | } 167 | 168 | // These are low level functions that call the interrupt handler for the correct 169 | // instance of BG_RF95. 170 | // 3 interrupts allows us to have 3 different devices 171 | void BG_RF95::isr0() 172 | { 173 | if (_deviceForInterrupt[0]) 174 | _deviceForInterrupt[0]->handleInterrupt(); 175 | } 176 | void BG_RF95::isr1() 177 | { 178 | if (_deviceForInterrupt[1]) 179 | _deviceForInterrupt[1]->handleInterrupt(); 180 | } 181 | void BG_RF95::isr2() 182 | { 183 | if (_deviceForInterrupt[2]) 184 | _deviceForInterrupt[2]->handleInterrupt(); 185 | } 186 | 187 | // Check whether the latest received message is complete and uncorrupted 188 | void BG_RF95::validateRxBuf() 189 | { 190 | _promiscuous = 1; 191 | if (_bufLen < 4) 192 | return; // Too short to be a real message 193 | // Extract the 4 headers 194 | //Serial.println("validateRxBuf >= 4"); 195 | _rxHeaderTo = _buf[0]; 196 | _rxHeaderFrom = _buf[1]; 197 | _rxHeaderId = _buf[2]; 198 | _rxHeaderFlags = _buf[3]; 199 | if (_promiscuous || 200 | _rxHeaderTo == _thisAddress || 201 | _rxHeaderTo == RH_BROADCAST_ADDRESS) 202 | { 203 | _rxGood++; 204 | _rxBufValid = true; 205 | } 206 | } 207 | 208 | bool BG_RF95::available() 209 | { 210 | if (_mode == RHModeTx) 211 | return false; 212 | setModeRx(); 213 | return _rxBufValid; // Will be set by the interrupt handler when a good message is received 214 | } 215 | 216 | void BG_RF95::clearRxBuf() 217 | { 218 | ATOMIC_BLOCK_START; 219 | _rxBufValid = false; 220 | _bufLen = 0; 221 | ATOMIC_BLOCK_END; 222 | } 223 | 224 | 225 | // BG 3 Byte header 226 | bool BG_RF95::recvAPRS(uint8_t* buf, uint8_t* len) 227 | { 228 | if (!available()) 229 | return false; 230 | if (buf && len) 231 | { 232 | ATOMIC_BLOCK_START; 233 | // Skip the 4 headers that are at the beginning of the rxBuf 234 | if (*len > _bufLen-BG_RF95_HEADER_LEN) 235 | *len = _bufLen-(BG_RF95_HEADER_LEN-1); 236 | memcpy(buf, _buf+(BG_RF95_HEADER_LEN-1), *len); // BG only 3 Byte header (-1) 237 | ATOMIC_BLOCK_END; 238 | } 239 | clearRxBuf(); // This message accepted and cleared 240 | return true; 241 | } 242 | 243 | bool BG_RF95::recv(uint8_t* buf, uint8_t* len) 244 | { 245 | if (!available()) 246 | return false; 247 | if (buf && len) 248 | { 249 | ATOMIC_BLOCK_START; 250 | // Skip the 4 headers that are at the beginning of the rxBuf 251 | if (*len > _bufLen-BG_RF95_HEADER_LEN) 252 | *len = _bufLen-BG_RF95_HEADER_LEN; 253 | memcpy(buf, _buf+BG_RF95_HEADER_LEN, *len); 254 | ATOMIC_BLOCK_END; 255 | } 256 | clearRxBuf(); // This message accepted and cleared 257 | return true; 258 | } 259 | 260 | uint8_t BG_RF95::lastSNR() 261 | { 262 | return(_lastSNR); 263 | } 264 | 265 | 266 | bool BG_RF95::send(const uint8_t* data, uint8_t len) 267 | { 268 | if (len > BG_RF95_MAX_MESSAGE_LEN) 269 | return false; 270 | 271 | waitPacketSent(); // Make sure we dont interrupt an outgoing message 272 | setModeIdle(); 273 | 274 | // Position at the beginning of the FIFO 275 | spiWrite(BG_RF95_REG_0D_FIFO_ADDR_PTR, 0); 276 | // The headers 277 | spiWrite(BG_RF95_REG_00_FIFO, _txHeaderTo); 278 | spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFrom); 279 | spiWrite(BG_RF95_REG_00_FIFO, _txHeaderId); 280 | spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFlags); 281 | // The message data 282 | spiBurstWrite(BG_RF95_REG_00_FIFO, data, len); 283 | spiWrite(BG_RF95_REG_22_PAYLOAD_LENGTH, len + BG_RF95_HEADER_LEN); 284 | 285 | setModeTx(); // Start the transmitter 286 | // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY 287 | return true; 288 | } 289 | 290 | bool BG_RF95::sendAPRS(const uint8_t* data, uint8_t len) 291 | { 292 | if (len > BG_RF95_MAX_MESSAGE_LEN) 293 | return false; 294 | 295 | waitPacketSent(); // Make sure we dont interrupt an outgoing message 296 | setModeIdle(); 297 | 298 | // Position at the beginning of the FIFO 299 | spiWrite(BG_RF95_REG_0D_FIFO_ADDR_PTR, 0); 300 | // The headers for APRS 301 | spiWrite(BG_RF95_REG_00_FIFO, '<'); 302 | spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFrom); 303 | spiWrite(BG_RF95_REG_00_FIFO, 0x1 ); 304 | //spiWrite(BG_RF95_REG_00_FIFO, _txHeaderFlags); 305 | // The message data 306 | spiBurstWrite(BG_RF95_REG_00_FIFO, data, len); 307 | spiWrite(BG_RF95_REG_22_PAYLOAD_LENGTH, len + BG_RF95_HEADER_LEN -1 ); // only 3 Byte header BG 308 | 309 | setModeTx(); // Start the transmitter 310 | // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY 311 | return true; 312 | } 313 | 314 | bool BG_RF95::printRegisters() 315 | { 316 | #ifdef RH_HAVE_SERIAL 317 | 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 }; 318 | 319 | uint8_t i; 320 | for (i = 0; i < sizeof(registers); i++) 321 | { 322 | Serial.print(registers[i], HEX); 323 | Serial.print(": "); 324 | Serial.println(spiRead(registers[i]), HEX); 325 | } 326 | #endif 327 | return true; 328 | } 329 | 330 | uint8_t BG_RF95::maxMessageLength() 331 | { 332 | return BG_RF95_MAX_MESSAGE_LEN; 333 | } 334 | 335 | bool BG_RF95::setFrequency(float centre) 336 | { 337 | // Frf = FRF / FSTEP 338 | uint32_t frf = (centre * 1000000.0) / BG_RF95_FSTEP; 339 | spiWrite(BG_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff); 340 | spiWrite(BG_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff); 341 | spiWrite(BG_RF95_REG_08_FRF_LSB, frf & 0xff); 342 | 343 | return true; 344 | } 345 | 346 | void BG_RF95::setModeIdle() 347 | { 348 | if (_mode != RHModeIdle) 349 | { 350 | spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_STDBY); 351 | _mode = RHModeIdle; 352 | } 353 | } 354 | 355 | 356 | bool BG_RF95::sleep() 357 | { 358 | if (_mode != RHModeSleep) 359 | { 360 | spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_SLEEP); 361 | _mode = RHModeSleep; 362 | } 363 | return true; 364 | } 365 | 366 | void BG_RF95::setModeRx() 367 | { 368 | if (_mode != RHModeRx) 369 | { 370 | //Serial.println("SetModeRx"); 371 | _mode = RHModeRx; 372 | spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_RXCONTINUOUS); 373 | spiWrite(BG_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone 374 | } 375 | } 376 | 377 | void BG_RF95::setModeTx() 378 | { 379 | if (_mode != RHModeTx) 380 | { 381 | _mode = RHModeTx; // set first to avoid possible race condition 382 | spiWrite(BG_RF95_REG_01_OP_MODE, BG_RF95_MODE_TX); 383 | spiWrite(BG_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone 384 | } 385 | } 386 | 387 | void BG_RF95::setTxPower(int8_t power, bool useRFO) 388 | { 389 | // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin 390 | // for the transmitter output 391 | if (useRFO) 392 | { 393 | if (power > 14) power = 14; 394 | if (power < -1) power = -1; 395 | spiWrite(BG_RF95_REG_09_PA_CONFIG, BG_RF95_MAX_POWER | (power + 1)); 396 | } else { 397 | if (power > 23) power = 23; 398 | if (power < 5) power = 5; 399 | 400 | // For BG_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' 401 | // BG_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it 402 | // for 21, 22 and 23dBm -= 3; 403 | } 404 | if (power > 20) { 405 | spiWrite(BG_RF95_REG_0B_OCP, ( BG_RF95_OCP_ON | BG_RF95_OCP_TRIM ) ); // Trim max current tp 240mA 406 | spiWrite(BG_RF95_REG_4D_PA_DAC, BG_RF95_PA_DAC_ENABLE); 407 | //power -= 3; 408 | power = 20; // and set PA_DAC_ENABLE 409 | 410 | } else { 411 | spiWrite(BG_RF95_REG_4D_PA_DAC, BG_RF95_PA_DAC_DISABLE); 412 | } 413 | 414 | // RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST 415 | // pin is connected, so must use PA_BOOST 416 | // Pout = 2 + OutputPower. 417 | // The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm, 418 | // but OutputPower claims it would be 17dBm. 419 | // My measurements show 20dBm is correct 420 | //spiWrite(BG_RF95_REG_09_PA_CONFIG, (BG_RF95_PA_SELECT | (power-5)) ); 421 | spiWrite(BG_RF95_REG_09_PA_CONFIG, (BG_RF95_PA_SELECT | BG_RF95_MAX_POWER | (power-5)) ); 422 | 423 | //} 424 | } 425 | 426 | // Sets registers from a canned modem configuration structure 427 | void BG_RF95::setModemRegisters(const ModemConfig* config) 428 | { 429 | spiWrite(BG_RF95_REG_1D_MODEM_CONFIG1, config->reg_1d); 430 | spiWrite(BG_RF95_REG_1E_MODEM_CONFIG2, config->reg_1e); 431 | spiWrite(BG_RF95_REG_26_MODEM_CONFIG3, config->reg_26); 432 | } 433 | 434 | // Set one of the canned FSK Modem configs 435 | // Returns true if its a valid choice 436 | bool BG_RF95::setModemConfig(ModemConfigChoice index) 437 | { 438 | if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) 439 | return false; 440 | 441 | ModemConfig cfg; 442 | memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(BG_RF95::ModemConfig)); 443 | setModemRegisters(&cfg); 444 | 445 | return true; 446 | } 447 | 448 | void BG_RF95::setPreambleLength(uint16_t bytes) 449 | { 450 | spiWrite(BG_RF95_REG_20_PREAMBLE_MSB, bytes >> 8); 451 | spiWrite(BG_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff); 452 | } 453 | -------------------------------------------------------------------------------- /lib/BG_RF95/BG_RF95.h: -------------------------------------------------------------------------------- 1 | // BG_RF95.h 2 | // 3 | // Definitions for HopeRF LoRa radios per: 4 | // http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf 5 | // http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf 6 | // 7 | // Author: Mike McCauley (mikem@airspayce.com) 8 | // Copyright (C) 2014 Mike McCauley 9 | // $Id: BG_RF95.h,v 1.11 2016/07/07 00:02:53 mikem Exp mikem $ 10 | // modified for Lora APRS Bernd Gasser OE1ACM 11 | 12 | #ifndef BG_RF95_h 13 | #define BG_RF95_h 14 | 15 | #include 16 | 17 | 18 | 19 | 20 | 21 | // This is the maximum number of interrupts the driver can support 22 | // Most Arduinos can handle 2, Megas can handle more 23 | #define BG_RF95_NUM_INTERRUPTS 3 24 | 25 | // Max number of octets the LORA Rx/Tx FIFO can hold 26 | #define BG_RF95_FIFO_SIZE 255 27 | 28 | // This is the maximum number of bytes that can be carried by the LORA. 29 | // We use some for headers, keeping fewer for RadioHead messages 30 | #define BG_RF95_MAX_PAYLOAD_LEN BG_RF95_FIFO_SIZE 31 | 32 | // The length of the headers we add. 33 | // The headers are inside the LORA's payload 34 | #define BG_RF95_HEADER_LEN 4 35 | 36 | // This is the maximum message length that can be supported by this driver. 37 | // Can be pre-defined to a smaller size (to save SRAM) prior to including this header 38 | // Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS 39 | #ifndef BG_RF95_MAX_MESSAGE_LEN 40 | #define BG_RF95_MAX_MESSAGE_LEN (BG_RF95_MAX_PAYLOAD_LEN - BG_RF95_HEADER_LEN) 41 | #endif 42 | 43 | // The crystal oscillator frequency of the module 44 | #define BG_RF95_FXOSC 32000000.0 45 | 46 | // The Frequency Synthesizer step = BG_RF95_FXOSC / 2^^19 47 | #define BG_RF95_FSTEP (BG_RF95_FXOSC / 524288) 48 | 49 | 50 | // Register names (LoRa Mode, from table 85) 51 | #define BG_RF95_REG_00_FIFO 0x00 52 | #define BG_RF95_REG_01_OP_MODE 0x01 53 | #define BG_RF95_REG_02_RESERVED 0x02 54 | #define BG_RF95_REG_03_RESERVED 0x03 55 | #define BG_RF95_REG_04_RESERVED 0x04 56 | #define BG_RF95_REG_05_RESERVED 0x05 57 | #define BG_RF95_REG_06_FRF_MSB 0x06 58 | #define BG_RF95_REG_07_FRF_MID 0x07 59 | #define BG_RF95_REG_08_FRF_LSB 0x08 60 | #define BG_RF95_REG_09_PA_CONFIG 0x09 61 | #define BG_RF95_REG_0A_PA_RAMP 0x0a 62 | #define BG_RF95_REG_0B_OCP 0x0b 63 | #define BG_RF95_REG_0C_LNA 0x0c 64 | #define BG_RF95_REG_0D_FIFO_ADDR_PTR 0x0d 65 | #define BG_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e 66 | #define BG_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f 67 | #define BG_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 68 | #define BG_RF95_REG_11_IRQ_FLAGS_MASK 0x11 69 | #define BG_RF95_REG_12_IRQ_FLAGS 0x12 70 | #define BG_RF95_REG_13_RX_NB_BYTES 0x13 71 | #define BG_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 72 | #define BG_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 73 | #define BG_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 74 | #define BG_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 75 | #define BG_RF95_REG_18_MODEM_STAT 0x18 76 | #define BG_RF95_REG_19_PKT_SNR_VALUE 0x19 77 | #define BG_RF95_REG_1A_PKT_RSSI_VALUE 0x1a 78 | #define BG_RF95_REG_1B_RSSI_VALUE 0x1b 79 | #define BG_RF95_REG_1C_HOP_CHANNEL 0x1c 80 | #define BG_RF95_REG_1D_MODEM_CONFIG1 0x1d 81 | #define BG_RF95_REG_1E_MODEM_CONFIG2 0x1e 82 | #define BG_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f 83 | #define BG_RF95_REG_20_PREAMBLE_MSB 0x20 84 | #define BG_RF95_REG_21_PREAMBLE_LSB 0x21 85 | #define BG_RF95_REG_22_PAYLOAD_LENGTH 0x22 86 | #define BG_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 87 | #define BG_RF95_REG_24_HOP_PERIOD 0x24 88 | #define BG_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 89 | #define BG_RF95_REG_26_MODEM_CONFIG3 0x26 90 | 91 | #define BG_RF95_REG_40_DIO_MAPPING1 0x40 92 | #define BG_RF95_REG_41_DIO_MAPPING2 0x41 93 | #define BG_RF95_REG_42_VERSION 0x42 94 | 95 | #define BG_RF95_REG_4B_TCXO 0x4b 96 | #define BG_RF95_REG_4D_PA_DAC 0x4d 97 | #define BG_RF95_REG_5B_FORMER_TEMP 0x5b 98 | #define BG_RF95_REG_61_AGC_REF 0x61 99 | #define BG_RF95_REG_62_AGC_THRESH1 0x62 100 | #define BG_RF95_REG_63_AGC_THRESH2 0x63 101 | #define BG_RF95_REG_64_AGC_THRESH3 0x64 102 | 103 | // BG_RF95_REG_01_OP_MODE 0x01 104 | #define BG_RF95_LONG_RANGE_MODE 0x80 105 | #define BG_RF95_ACCESS_SHARED_REG 0x40 106 | #define BG_RF95_MODE 0x07 107 | #define BG_RF95_MODE_SLEEP 0x00 108 | #define BG_RF95_MODE_STDBY 0x01 109 | #define BG_RF95_MODE_FSTX 0x02 110 | #define BG_RF95_MODE_TX 0x03 111 | #define BG_RF95_MODE_FSRX 0x04 112 | #define BG_RF95_MODE_RXCONTINUOUS 0x05 113 | #define BG_RF95_MODE_RXSINGLE 0x06 114 | #define BG_RF95_MODE_CAD 0x07 115 | 116 | // BG_RF95_REG_09_PA_CONFIG 0x09 117 | #define BG_RF95_PA_SELECT 0x80 118 | #define BG_RF95_MAX_POWER 0x70 119 | #define BG_RF95_OUTPUT_POWER 0x0f 120 | 121 | // BG_RF95_REG_0A_PA_RAMP 0x0a 122 | #define BG_RF95_LOW_PN_TX_PLL_OFF 0x10 123 | #define BG_RF95_PA_RAMP 0x0f 124 | #define BG_RF95_PA_RAMP_3_4MS 0x00 125 | #define BG_RF95_PA_RAMP_2MS 0x01 126 | #define BG_RF95_PA_RAMP_1MS 0x02 127 | #define BG_RF95_PA_RAMP_500US 0x03 128 | #define BG_RF95_PA_RAMP_250US 0x0 129 | #define BG_RF95_PA_RAMP_125US 0x05 130 | #define BG_RF95_PA_RAMP_100US 0x06 131 | #define BG_RF95_PA_RAMP_62US 0x07 132 | #define BG_RF95_PA_RAMP_50US 0x08 133 | #define BG_RF95_PA_RAMP_40US 0x09 134 | #define BG_RF95_PA_RAMP_31US 0x0a 135 | #define BG_RF95_PA_RAMP_25US 0x0b 136 | #define BG_RF95_PA_RAMP_20US 0x0c 137 | #define BG_RF95_PA_RAMP_15US 0x0d 138 | #define BG_RF95_PA_RAMP_12US 0x0e 139 | #define BG_RF95_PA_RAMP_10US 0x0f 140 | 141 | // BG_RF95_REG_0B_OCP 0x0b 142 | #define BG_RF95_OCP_ON 0x20 143 | #define BG_RF95_OCP_TRIM 0x1f 144 | 145 | // BG_RF95_REG_0C_LNA 0x0c 146 | #define BG_RF95_LNA_GAIN 0xe0 147 | #define BG_RF95_LNA_BOOST 0x03 148 | #define BG_RF95_LNA_BOOST_DEFAULT 0x00 149 | #define BG_RF95_LNA_BOOST_150PC 0x11 150 | 151 | // BG_RF95_REG_11_IRQ_FLAGS_MASK 0x11 152 | #define BG_RF95_RX_TIMEOUT_MASK 0x80 153 | #define BG_RF95_RX_DONE_MASK 0x40 154 | #define BG_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 155 | #define BG_RF95_VALID_HEADER_MASK 0x10 156 | #define BG_RF95_TX_DONE_MASK 0x08 157 | #define BG_RF95_CAD_DONE_MASK 0x04 158 | #define BG_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 159 | #define BG_RF95_CAD_DETECTED_MASK 0x01 160 | 161 | // BG_RF95_REG_12_IRQ_FLAGS 0x12 162 | #define BG_RF95_RX_TIMEOUT 0x80 163 | #define BG_RF95_RX_DONE 0x40 164 | #define BG_RF95_PAYLOAD_CRC_ERROR 0x20 165 | #define BG_RF95_VALID_HEADER 0x10 166 | #define BG_RF95_TX_DONE 0x08 167 | #define BG_RF95_CAD_DONE 0x04 168 | #define BG_RF95_FHSS_CHANGE_CHANNEL 0x02 169 | #define BG_RF95_CAD_DETECTED 0x01 170 | 171 | // BG_RF95_REG_18_MODEM_STAT 0x18 172 | #define BG_RF95_RX_CODING_RATE 0xe0 173 | #define BG_RF95_MODEM_STATUS_CLEAR 0x10 174 | #define BG_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 175 | #define BG_RF95_MODEM_STATUS_RX_ONGOING 0x04 176 | #define BG_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 177 | #define BG_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 178 | 179 | // BG_RF95_REG_1C_HOP_CHANNEL 0x1c 180 | #define BG_RF95_PLL_TIMEOUT 0x80 181 | #define BG_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 182 | #define BG_RF95_FHSS_PRESENT_CHANNEL 0x3f 183 | 184 | // BG_RF95_REG_1D_MODEM_CONFIG1 0x1d 185 | #define BG_RF95_BW 0xc0 186 | #define BG_RF95_BW_125KHZ 0x00 187 | #define BG_RF95_BW_250KHZ 0x40 188 | #define BG_RF95_BW_500KHZ 0x80 189 | #define BG_RF95_BW_RESERVED 0xc0 190 | #define BG_RF95_CODING_RATE 0x38 191 | #define BG_RF95_CODING_RATE_4_5 0x00 192 | #define BG_RF95_CODING_RATE_4_6 0x08 193 | #define BG_RF95_CODING_RATE_4_7 0x10 194 | #define BG_RF95_CODING_RATE_4_8 0x18 195 | #define BG_RF95_IMPLICIT_HEADER_MODE_ON 0x04 196 | #define BG_RF95_RX_PAYLOAD_CRC_ON 0x02 197 | #define BG_RF95_LOW_DATA_RATE_OPTIMIZE 0x01 198 | 199 | // BG_RF95_REG_1E_MODEM_CONFIG2 0x1e 200 | #define BG_RF95_SPREADING_FACTOR 0xf0 201 | #define BG_RF95_SPREADING_FACTOR_64CPS 0x60 202 | #define BG_RF95_SPREADING_FACTOR_128CPS 0x70 203 | #define BG_RF95_SPREADING_FACTOR_256CPS 0x80 204 | #define BG_RF95_SPREADING_FACTOR_512CPS 0x90 205 | #define BG_RF95_SPREADING_FACTOR_1024CPS 0xa0 206 | #define BG_RF95_SPREADING_FACTOR_2048CPS 0xb0 207 | #define BG_RF95_SPREADING_FACTOR_4096CPS 0xc0 208 | #define BG_RF95_TX_CONTINUOUS_MOE 0x08 209 | #define BG_RF95_AGC_AUTO_ON 0x04 210 | #define BG_RF95_SYM_TIMEOUT_MSB 0x03 211 | 212 | // BG_RF95_REG_4D_PA_DAC 0x4d 213 | #define BG_RF95_PA_DAC_DISABLE 0x04 214 | #define BG_RF95_PA_DAC_ENABLE 0x07 215 | 216 | ///////////////////////////////////////////////////////////////////// 217 | /// \class BG_RF95 BG_RF95.h 218 | /// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa 219 | /// capable radio transceiver. 220 | /// 221 | /// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios. 222 | /// Based on http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf 223 | /// and http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf 224 | /// and http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf 225 | /// and http://www.semtech.com/images/datasheet/sx1276.pdf 226 | /// and http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf 227 | /// FSK/GFSK/OOK modes are not (yet) supported. 228 | /// 229 | /// Works with 230 | /// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless 231 | /// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html 232 | /// and inAir9 modules http://modtronix.com/inair9.html. 233 | /// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W 234 | /// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ 235 | /// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99 236 | /// - Adafruit Feather M0 with RFM95 237 | /// 238 | /// \par Overview 239 | /// 240 | /// This class provides basic functions for sending and receiving unaddressed, 241 | /// unreliable datagrams of arbitrary length to 251 octets per packet. 242 | /// 243 | /// Manager classes may use this class to implement reliable, addressed datagrams and streams, 244 | /// mesh routers, repeaters, translators etc. 245 | /// 246 | /// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and 247 | /// modulation scheme. 248 | /// 249 | /// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF 250 | /// RFM95/96/97/98(W), Semtech SX1276/77/78/79 and compatible radio modules in LoRa mode. 251 | /// 252 | /// The Hope-RF (http://www.hoperf.com) RFM95/96/97/98(W) and Semtech SX1276/77/78/79 is a low-cost ISM transceiver 253 | /// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and 254 | /// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which 255 | /// is the only mode supported in this RadioHead driver. 256 | /// 257 | /// This Driver provides functions for sending and receiving messages of up 258 | /// to 251 octets on any frequency supported by the radio, in a range of 259 | /// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with 260 | /// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited 261 | /// range of frequencies due to antenna tuning. 262 | /// 263 | /// Up to 2 modules can be connected to an Arduino (3 on a Mega), 264 | /// permitting the construction of translators and frequency changers, etc. 265 | /// 266 | /// Support for other features such as transmitter power control etc is 267 | /// also provided. 268 | /// 269 | /// Tested on MinWirelessLoRa with arduino-1.0.5 270 | /// on OpenSuSE 13.1. 271 | /// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1 272 | /// 273 | /// \par Packet Format 274 | /// 275 | /// All messages sent and received by this BG_RF95 Driver conform to this packet format: 276 | /// 277 | /// - LoRa mode: 278 | /// - 8 symbol PREAMBLE 279 | /// - Explicit header with header CRC (handled internally by the radio) 280 | /// - 4 octets HEADER: (TO, FROM, ID, FLAGS) 281 | /// - 0 to 251 octets DATA 282 | /// - CRC (handled internally by the radio) 283 | /// 284 | /// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino 285 | /// 286 | /// We tested with Anarduino MiniWirelessLoRA, which is an Arduino Duemilanove compatible with a RFM96W 287 | /// module on-board. Therefore it needs no connections other than the USB 288 | /// programming connection and an antenna to make it work. 289 | /// 290 | /// If you have a bare RFM95/96/97/98 that you want to connect to an Arduino, you 291 | /// might use these connections (untested): CAUTION: you must use a 3.3V type 292 | /// Arduino, otherwise you will also need voltage level shifters between the 293 | /// Arduino and the RFM95. CAUTION, you must also ensure you connect an 294 | /// antenna. 295 | /// 296 | /// \code 297 | /// Arduino RFM95/96/97/98 298 | /// GND----------GND (ground in) 299 | /// 3V3----------3.3V (3.3V in) 300 | /// 301 | 302 | /// SS pin D10----------NSS (CS chip select in) 303 | /// SCK pin D13----------SCK (SPI clock in) 304 | /// MOSI pin D11----------MOSI (SPI Data in) 305 | /// MISO pin D12----------MISO (SPI Data out) 306 | /// \endcode 307 | /// With these connections, you can then use the default constructor BG_RF95(). 308 | /// You can override the default settings for the SS pin and the interrupt in 309 | /// the BG_RF95 constructor if you wish to connect the slave select SS to other 310 | /// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53 311 | /// for Mega) or the interrupt request to other than pin D2 (Caution, 312 | /// different processors have different constraints as to the pins available 313 | /// for interrupts). 314 | /// 315 | /// You can connect a Modtronix inAir4 or inAir9 directly to a 3.3V part such as a Teensy 3.1 like 316 | /// this (tested). 317 | /// \code 318 | /// Teensy inAir4 inAir9 319 | /// GND----------GND (ground in) 320 | /// 3V3----------3.3V (3.3V in) 321 | /// interrupt 0 pin D2-----------D00 (interrupt request out) 322 | /// SS pin D10----------CS (CS chip select in) 323 | /// SCK pin D13----------CK (SPI clock in) 324 | /// MOSI pin D11----------SI (SPI Data in) 325 | /// MISO pin D12----------SO (SPI Data out) 326 | /// \endcode 327 | /// With these connections, you can then use the default constructor BG_RF95(). 328 | /// you must also set the transmitter power with useRFO: 329 | /// driver.setTxPower(13, true); 330 | /// 331 | /// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the 332 | /// transmitter RFO pins and not the PA_BOOST pins 333 | /// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. 334 | /// Failure to do that will result in extremely low transmit powers. 335 | /// 336 | /// If you have an Arduino M0 Pro from arduino.org, 337 | /// you should note that you cannot use Pin 2 for the interrupt line 338 | /// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. 339 | /// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: 340 | /// \code 341 | /// // Slave Select is pin 10, interrupt is Pin 3 342 | /// BG_RF95 driver(10, 3); 343 | /// \endcode 344 | /// 345 | /// If you have a Rocket Scream Mini Ultra Pro with the RFM95W: 346 | /// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. 347 | /// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, 348 | /// so you need to initialise the radio like this: 349 | /// \code 350 | /// BG_RF95 driver(5, 2); 351 | /// \endcode 352 | /// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our 353 | /// sample sketches: 354 | /// \code 355 | /// #define Serial SerialUSB 356 | /// \endcode 357 | /// - You also need this in setup before radio initialisation 358 | /// \code 359 | /// // Ensure serial flash is not interfering with radio communication on SPI bus 360 | /// pinMode(4, OUTPUT); 361 | /// digitalWrite(4, HIGH); 362 | /// \endcode 363 | /// - and if you have a 915MHz part, you need this after driver/manager intitalisation: 364 | /// \code 365 | /// rf95.setFrequency(915.0); 366 | /// \endcode 367 | /// which adds up to modifying sample sketches something like: 368 | /// \code 369 | /// #include 370 | /// #include 371 | /// BG_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W 372 | /// #define Serial SerialUSB 373 | /// 374 | /// void setup() 375 | /// { 376 | /// // Ensure serial flash is not interfering with radio communication on SPI bus 377 | /// pinMode(4, OUTPUT); 378 | /// digitalWrite(4, HIGH); 379 | /// 380 | /// Serial.begin(9600); 381 | /// while (!Serial) ; // Wait for serial port to be available 382 | /// if (!rf95.init()) 383 | /// Serial.println("init failed"); 384 | /// rf95.setFrequency(915.0); 385 | /// } 386 | /// ... 387 | /// \endcode 388 | /// 389 | /// For Adafruit Feather M0 with RFM95, construct the driver like this: 390 | /// \code 391 | /// BG_RF95 rf95(8, 3); 392 | /// \endcode 393 | /// 394 | /// It is possible to have 2 or more radios connected to one Arduino, provided 395 | /// each radio has its own SS and interrupt line (SCK, SDI and SDO are common 396 | /// to all radios) 397 | /// 398 | /// Caution: on some Arduinos such as the Mega 2560, if you set the slave 399 | /// select pin to be other than the usual SS pin (D53 on Mega 2560), you may 400 | /// need to set the usual SS pin to be an output to force the Arduino into SPI 401 | /// master mode. 402 | /// 403 | /// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: 404 | /// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can 405 | /// give 50mA. You may need to make provision for alternate power supply for 406 | /// the RFM module, especially if you wish to use full transmit power, and/or you have 407 | /// other shields demanding power. Inadequate power for the RFM is likely to cause symptoms such as: 408 | /// - reset's/bootups terminate with "init failed" messages 409 | /// - random termination of communication after 5-30 packets sent/received 410 | /// - "fake ok" state, where initialization passes fluently, but communication doesn't happen 411 | /// - shields hang Arduino boards, especially during the flashing 412 | /// 413 | /// \par Interrupts 414 | /// 415 | /// The BG_RF95 driver uses interrupts to react to events in the RFM module, 416 | /// such as the reception of a new packet, or the completion of transmission 417 | /// of a packet. The BG_RF95 driver interrupt service routine reads status from 418 | /// and writes data to the the RFM module via the SPI interface. It is very 419 | /// important therefore, that if you are using the BG_RF95 driver with another 420 | /// SPI based deviced, that you disable interrupts while you transfer data to 421 | /// and from that other device. Use cli() to disable interrupts and sei() to 422 | /// reenable them. 423 | /// 424 | /// \par Memory 425 | /// 426 | /// The BG_RF95 driver requires non-trivial amounts of memory. The sample 427 | /// programs all compile to about 8kbytes each, which will fit in the 428 | /// flash proram memory of most Arduinos. However, the RAM requirements are 429 | /// more critical. Therefore, you should be vary sparing with RAM use in 430 | /// programs that use the BG_RF95 driver. 431 | /// 432 | /// It is often hard to accurately identify when you are hitting RAM limits on Arduino. 433 | /// The symptoms can include: 434 | /// - Mysterious crashes and restarts 435 | /// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) 436 | /// - Hanging 437 | /// - Output from Serial.print() not appearing 438 | /// 439 | /// \par Range 440 | /// 441 | /// We have made some simple range tests under the following conditions: 442 | /// - rf95_client base station connected to a VHF discone antenna at 8m height above ground 443 | /// - rf95_server mobile connected to 17.3cm 1/4 wavelength antenna at 1m height, no ground plane. 444 | /// - Both configured for 13dBm, 434MHz, Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range 445 | /// - Minimum reported RSSI seen for successful comms was about -91 446 | /// - Range over flat ground through heavy trees and vegetation approx 2km. 447 | /// - At 20dBm (100mW) otherwise identical conditions approx 3km. 448 | /// - At 20dBm, along salt water flat sandy beach, 3.2km. 449 | /// 450 | /// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit. 451 | /// 452 | /// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. 453 | /// (Default medium range) in the conditions described above. 454 | /// - Range over flat ground through heavy trees and vegetation approx 2km. 455 | /// 456 | /// \par Transmitter Power 457 | /// 458 | /// You can control the transmitter power on the RF transceiver 459 | /// with the BG_RF95::setTxPower() function. The argument can be any of 460 | /// +5 to +23 (for modules that use PA_BOOST) 461 | /// -1 to +14 (for modules that use RFO transmitter pin) 462 | /// The default is 13. Eg: 463 | /// \code 464 | /// driver.setTxPower(10); // use PA_BOOST transmitter pin 465 | /// driver.setTxPower(10, true); // use PA_RFO pin transmitter pin 466 | /// \endcode 467 | /// 468 | /// We have made some actual power measurements against 469 | /// programmed power for Anarduino MiniWirelessLoRa (which has RFM96W-433Mhz installed) 470 | /// - MiniWirelessLoRa RFM96W-433Mhz, USB power 471 | /// - 30cm RG316 soldered direct to RFM96W module ANT and GND 472 | /// - SMA connector 473 | /// - 12db attenuator 474 | /// - SMA connector 475 | /// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) 476 | /// - Tektronix TDS220 scope to measure the Vout from power head 477 | /// \code 478 | /// Program power Measured Power 479 | /// dBm dBm 480 | /// 5 5 481 | /// 7 7 482 | /// 9 8 483 | /// 11 11 484 | /// 13 13 485 | /// 15 15 486 | /// 17 16 487 | /// 19 18 488 | /// 20 20 489 | /// 21 21 490 | /// 22 22 491 | /// 23 23 492 | /// \endcode 493 | /// 494 | /// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html 495 | /// connected to a Teensy 3.1: 496 | /// Teensy 3.1 this is a 3.3V part, connected directly to: 497 | /// Modtronix inAir4 with SMA antenna connector, connected as above: 498 | /// 10cm SMA-SMA cable 499 | /// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) 500 | /// - Tektronix TDS220 scope to measure the Vout from power head 501 | /// \code 502 | /// Program power Measured Power 503 | /// dBm dBm 504 | /// -1 0 505 | /// 1 2 506 | /// 3 4 507 | /// 5 7 508 | /// 7 10 509 | /// 9 13 510 | /// 11 14.2 511 | /// 13 15 512 | /// 14 16 513 | /// \endcode 514 | /// (Caution: we dont claim laboratory accuracy for these power measurements) 515 | /// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. 516 | class BG_RF95 : public RHSPIDriver 517 | { 518 | public: 519 | /// \brief Defines register values for a set of modem configuration registers 520 | /// 521 | /// Defines register values for a set of modem configuration registers 522 | /// that can be passed to setModemRegisters() if none of the choices in 523 | /// ModemConfigChoice suit your need setModemRegisters() writes the 524 | /// register values from this structure to the appropriate registers 525 | /// to set the desired spreading factor, coding rate and bandwidth 526 | typedef struct 527 | { 528 | uint8_t reg_1d; ///< Value for register BG_RF95_REG_1D_MODEM_CONFIG1 529 | uint8_t reg_1e; ///< Value for register BG_RF95_REG_1E_MODEM_CONFIG2 530 | uint8_t reg_26; ///< Value for register BG_RF95_REG_26_MODEM_CONFIG3 531 | } ModemConfig; 532 | 533 | /// Choices for setModemConfig() for a selected subset of common 534 | /// data rates. If you need another configuration, 535 | /// determine the necessary settings and call setModemRegisters() with your 536 | /// desired settings. It might be helpful to use the LoRa calculator mentioned in 537 | /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf 538 | /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic 539 | /// definitions and not their integer equivalents: its possible that new values will be 540 | /// introduced in later versions (though we will try to avoid it). 541 | /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses 542 | /// you may need to change the RHReliableDatagram timeout for reliable operations. 543 | typedef enum 544 | { 545 | Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range 546 | Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range 547 | Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range 548 | Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range 549 | Bw125Cr45Sf4096, ///< APRS 550 | } ModemConfigChoice; 551 | 552 | /// Constructor. You can have multiple instances, but each instance must have its own 553 | /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface 554 | /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient 555 | /// distinct interrupt lines, one for each instance. 556 | /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before 557 | /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) 558 | /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. 559 | /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. 560 | /// Caution: You must specify an interrupt capable pin. 561 | /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. 562 | /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. 563 | /// On Arduino Zero from arduino.cc, any digital pin other than 4. 564 | /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. 565 | /// On other Arduinos pins 2 or 3. 566 | /// See http://arduino.cc/en/Reference/attachInterrupt for more details. 567 | /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. 568 | /// On other boards, any digital pin may be used. 569 | /// \param[in] spi Pointer to the SPI interface object to use. 570 | /// Defaults to the standard Arduino hardware SPI interface 571 | BG_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); 572 | 573 | /// Initialise the Driver transport hardware and software. 574 | /// Make sure the Driver is properly configured before calling init(). 575 | /// \return true if initialisation succeeded. 576 | virtual bool init(); 577 | 578 | /// Prints the value of all chip registers 579 | /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform 580 | /// For debugging purposes only. 581 | /// \return true on success 582 | bool printRegisters(); 583 | 584 | /// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth, 585 | /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the 586 | /// canned configurations in ModemConfigChoice suit you. 587 | /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. 588 | void setModemRegisters(const ModemConfig* config); 589 | 590 | /// Select one of the predefined modem configurations. If you need a modem configuration not provided 591 | /// here, use setModemRegisters() with your own ModemConfig. 592 | /// \param[in] index The configuration choice. 593 | /// \return true if index is a valid choice. 594 | bool setModemConfig(ModemConfigChoice index); 595 | 596 | /// Tests whether a new message is available 597 | /// from the Driver. 598 | /// On most drivers, this will also put the Driver into RHModeRx mode until 599 | /// a message is actually received by the transport, when it wil be returned to RHModeIdle. 600 | /// This can be called multiple times in a timeout loop 601 | /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() 602 | virtual bool available(); 603 | 604 | /// Turns the receiver on if it not already on. 605 | /// If there is a valid message available, copy it to buf and return true 606 | /// else return false. 607 | /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). 608 | /// You should be sure to call this function frequently enough to not miss any messages 609 | /// It is recommended that you call it in your main loop. 610 | /// \param[in] buf Location to copy the received message 611 | /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. 612 | /// \return true if a valid message was copied to buf 613 | virtual bool recv(uint8_t* buf, uint8_t* len); 614 | 615 | // added BG APRS Packets are sent with 3-Byte header 616 | // turn on promiscuous 617 | virtual bool recvAPRS(uint8_t* buf, uint8_t* len); 618 | 619 | /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). 620 | /// Then loads a message into the transmitter and starts the transmitter. Note that a message length 621 | /// of 0 is permitted. 622 | /// \param[in] data Array of data to be sent 623 | /// \param[in] len Number of bytes of data to send 624 | /// \return true if the message length was valid and it was correctly queued for transmit 625 | virtual bool send(const uint8_t* data, uint8_t len); 626 | 627 | // Send APRS Header Format 628 | virtual bool sendAPRS(const uint8_t* data, uint8_t len); 629 | 630 | virtual uint8_t lastSNR(); 631 | 632 | /// Sets the length of the preamble 633 | /// in bytes. 634 | /// Caution: this should be set to the same 635 | /// value on all nodes in your network. Default is 8. 636 | /// Sets the message preamble length in BG_RF95_REG_??_PREAMBLE_?SB 637 | /// \param[in] bytes Preamble length in bytes. 638 | void setPreambleLength(uint16_t bytes); 639 | 640 | /// Returns the maximum message length 641 | /// available in this Driver. 642 | /// \return The maximum legal message length 643 | virtual uint8_t maxMessageLength(); 644 | 645 | /// Sets the transmitter and receiver 646 | /// centre frequency. 647 | /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several 648 | /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work 649 | /// \return true if the selected frquency centre is within range 650 | bool setFrequency(float centre); 651 | 652 | /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, 653 | /// disables them. 654 | void setModeIdle(); 655 | 656 | /// If current mode is Tx or Idle, changes it to Rx. 657 | /// Starts the receiver in the RF95/96/97/98. 658 | void setModeRx(); 659 | 660 | /// If current mode is Rx or Idle, changes it to Rx. F 661 | /// Starts the transmitter in the RF95/96/97/98. 662 | void setModeTx(); 663 | 664 | /// Sets the transmitter power output level, and configures the transmitter pin. 665 | /// Be a good neighbour and set the lowest power level you need. 666 | /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) 667 | /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) 668 | /// while some (such as the Modtronix inAir4 and inAir9) 669 | /// use the RFO transmitter pin for lower power but higher efficiency. 670 | /// You must set the appropriate power level and useRFO argument for your module. 671 | /// Check with your module manufacturer which transmtter pin is used on your module 672 | /// to ensure you are setting useRFO correctly. 673 | /// Failure to do so will result in very low 674 | /// transmitter power output. 675 | /// Caution: legal power limits may apply in certain countries. 676 | /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). 677 | /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, 678 | /// valid values are from +5 to +23. 679 | /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), 680 | /// valid values are from -1 to 14. 681 | /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of 682 | /// the PA_BOOST pin (false). Choose the correct setting for your module. 683 | void setTxPower(int8_t power, bool useRFO = false); 684 | 685 | /// Sets the radio into low-power sleep mode. 686 | /// If successful, the transport will stay in sleep mode until woken by 687 | /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) 688 | /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. 689 | /// \return true if sleep mode was successfully entered. 690 | virtual bool sleep(); 691 | 692 | protected: 693 | /// This is a low level function to handle the interrupts for one instance of BG_RF95. 694 | /// Called automatically by isr*() 695 | /// Should not need to be called by user code. 696 | void handleInterrupt(); 697 | 698 | /// Examine the revceive buffer to determine whether the message is for this node 699 | void validateRxBuf(); 700 | 701 | /// Clear our local receive buffer 702 | void clearRxBuf(); 703 | 704 | private: 705 | /// Low level interrupt service routine for device connected to interrupt 0 706 | static void isr0(); 707 | 708 | /// Low level interrupt service routine for device connected to interrupt 1 709 | static void isr1(); 710 | 711 | /// Low level interrupt service routine for device connected to interrupt 1 712 | static void isr2(); 713 | 714 | /// Array of instances connected to interrupts 0 and 1 715 | static BG_RF95* _deviceForInterrupt[]; 716 | 717 | /// Index of next interrupt number to use in _deviceForInterrupt 718 | static uint8_t _interruptCount; 719 | 720 | /// The configured interrupt pin connected to this instance 721 | uint8_t _interruptPin; 722 | 723 | /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) 724 | /// else 0xff 725 | uint8_t _myInterruptIndex; 726 | 727 | /// Number of octets in the buffer 728 | volatile uint8_t _bufLen; 729 | 730 | /// The receiver/transmitter buffer 731 | uint8_t _buf[BG_RF95_MAX_PAYLOAD_LEN]; 732 | 733 | /// True when there is a valid message in the buffer 734 | volatile bool _rxBufValid; 735 | }; 736 | 737 | /// @example rf95_client.pde 738 | /// @example rf95_server.pde 739 | /// @example rf95_reliable_datagram_client.pde 740 | /// @example rf95_reliable_datagram_server.pde 741 | 742 | #endif 743 | 744 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:ttgo-t-beam] 12 | platform = espressif32 13 | board = ttgo-t-beam 14 | framework = arduino 15 | monitor_speed = 115200 16 | lib_deps = 17 | Adafruit BusIO 18 | RadioHead 19 | TinyGPSPlus 20 | DHT sensor library for ESPx 21 | Adafruit SSD1306 22 | Adafruit GFX Library 23 | Adafruit Unified Sensor 24 | AXP202X_Library 25 | OneWire 26 | DallasTemperature 27 | adafruit/Adafruit BME280 Library@^2.1.2 28 | -------------------------------------------------------------------------------- /src/TTGO_T-Beam_LoRa_APRS.ino: -------------------------------------------------------------------------------- 1 | 2 | // Tracker for LoRA APRS 3 | // 4 | // TTGO T-Beam includes GPS module + optional DHT22 (not yet DONE) 5 | // 6 | // can be used as tracker only, tracker plus weather reports (temperature and humidity) or weather reports station only 7 | // 8 | // updated from OE1ACM sketch by OE3CJB to enable WX data to be sent via LoRa APRS. 9 | // one package is with position and battery voltage 10 | // the next is with weather data in APRS format 11 | // 12 | // licensed under CC BY-NC-SA 13 | // 14 | // version: V1.3 15 | // last update: 27.08.2020 16 | // change history 17 | // symbol RV added 18 | // compressed packets in tracker mode (base91) 19 | // 20 | // version: V1.2 21 | // last update: 02.01.2020 22 | // change history 23 | // added course change to smart Beaconing 24 | // code cleaned 25 | // change of mode with KEY (without display but with LED only) 26 | // change of symbol with KEY (without display but with LED only) 27 | // 28 | // version V1.1 29 | // added HW Version V1.0 support 30 | // added presetting in the header TTGO...config.h to prevent long initial setup at first boot up 31 | // added "SPACE" to allowed letters for callsign for shorter callsigns - has to be added at the end 32 | // added smart beaconing 33 | // 34 | // version V1.0beta 35 | // first released version// 36 | 37 | // #define DEBUG // used for debugging purposes , e.g. turning on special serial or display logging 38 | // Includes 39 | 40 | #include // to config user parameters 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include // library from OE1ACM 46 | 47 | #include 48 | #include 49 | #ifdef DS18B20 50 | #include // libraries for DS18B20 51 | #include 52 | #else 53 | #ifdef USE_BME280 54 | #include // BME280 Library 55 | #else 56 | #include // library from https://github.com/beegee-tokyo/DHTesp for DHT22 57 | #endif 58 | #endif 59 | #include 60 | #include 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | 71 | //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 72 | //PINs used for HW extensions 73 | 74 | // Pin for battery voltage -> bei T-Beam ADC1_CHANNEL_7 75 | // #define ANALOG_PIN_0 35 // connected to battery 76 | 77 | // I2C LINES 78 | #define I2C_SDA 21 79 | #define I2C_SCL 22 80 | 81 | // DISPLAY address 82 | #define SSD1306_ADDRESS 0x3C 83 | 84 | // AXP192 address 85 | // #define AXP192_SLAVE_ADDRESS 0x34 // already defined in axp20x.h 86 | 87 | /* for feather32u4 88 | #define RFM95_CS 8 89 | #define RFM95_RST 4 90 | #define RFM95_INT 7 91 | */ 92 | 93 | 94 | // Variables for DHT22 temperature and humidity sensor 95 | int chk; 96 | boolean hum_temp = false; 97 | uint8_t hum_temp_ctr, hum_temp_ctr_max = 3; 98 | float hum=0; //Stores humidity value 99 | float temp=99.99; //Stores temperature value 100 | float tempf=99.99; //Stores temperature value 101 | float pressure=0; //Stores pressure value in hPa 102 | int pressure_offset=0; //Stores offset for pressure correction 103 | 104 | //other global Variables 105 | String Textzeile1, Textzeile2; 106 | int button=0; 107 | int button_ctr=0; 108 | // int version=0; // 0 = V0.7, 1 = V1.0 109 | // bool ssd1306_found = false; 110 | // bool axp192_found = false; 111 | 112 | #define TRACKER 0 113 | #define WX_TRACKER 1 114 | #define WX_MOVE 2 115 | #define WX_FIXED 3 116 | // Position from GPS for TRACKER and WX_TRACKER 117 | // Position for WX_ONLY from Headerfile!!! 118 | 119 | uint8_t tracker_mode; 120 | 121 | // Pins for GPS 122 | #ifdef T_BEAM_V1_0 123 | static const int RXPin = 12, TXPin = 34; // changed BG A3 A2 124 | #else 125 | static const int RXPin = 15, TXPin = 12; // changed BG A3 A2 126 | #endif 127 | 128 | static const uint32_t GPSBaud = 9600; //GPS 129 | 130 | const byte TX_en = 0; 131 | const byte RX_en = 0; //TX/RX enable 1W modul 132 | 133 | // LED for signalling 134 | #ifdef T_BEAM_V1_0 135 | const byte TXLED = 4; //pin number for LED on TX Tracker 136 | #else 137 | const byte TXLED = 14; //pin number for LED on TX Tracker 138 | #endif 139 | 140 | // Button of TTGO T-Beam 141 | #ifdef T_BEAM_V1_0 142 | // const byte BUTTON = 38; //pin number for Button on TTGO T-Beam 143 | #define BUTTON 38 //pin number for Button on TTGO T-Beam 144 | #else 145 | #define BUTTON 39 //pin number for Button on TTGO T-Beam 146 | #endif 147 | 148 | // const byte GPSLED = 6; // pin gps & Heartbeat 149 | // const byte GPSLED1 = 9; // pin gps & Heartbeat 150 | 151 | // Pins for LoRa module 152 | //#ifdef T_BEAM_V1_0 153 | // const byte lora_PReset = 14; //pin where LoRa device reset line is connected 154 | // const byte lora_PNSS = 18; //pin number where the NSS line for the LoRa device is connected. 155 | //#else 156 | const byte lora_PReset = 23; //pin where LoRa device reset line is connected 157 | const byte lora_PNSS = 18; //pin number where the NSS line for the LoRa device is connected. 158 | //#endif 159 | // pin 11 MOSI 160 | // pin 12 MISO 161 | // pin 13 SCLK 162 | 163 | // #define ModemConfig BG_RF95::Bw125Cr45Sf4096 164 | 165 | #define DHTPIN 25 // the DHT22 is connected to PIN25 166 | #define ONE_WIRE_BUS 25 // the DS18B20 is connected to PIN25 167 | 168 | 169 | // Variables for APRS packaging 170 | String Tcall; //your Call Sign for normal position reports 171 | String wxTcall; //your Call Sign for weather reports 172 | String sTable="/"; //Primer 173 | String wxTable="/"; //Primer 174 | String wxSymbol="_"; //Symbol Code Weather Station 175 | 176 | // Tracker setting: use these lines to modify the tracker behaviour 177 | #define TXFREQ 433.775 // Transmit frequency in MHz 178 | #define TXdbmW 18 // Transmit power in dBm 179 | #define TXenablePA 0 // switch internal power amplifier on (1) or off (0) 180 | 181 | // Variables and Constants 182 | Preferences prefs; 183 | 184 | String InputString = ""; //data on buff is copied to this string 185 | String Outputstring = ""; 186 | String outString=""; //The new Output String with GPS Conversion RAW 187 | 188 | String LongShown=""; 189 | String LatShown=""; 190 | 191 | String LongFixed=""; 192 | String LatFixed=""; 193 | 194 | String TxSymbol=""; 195 | 196 | boolean wx; 197 | 198 | //byte arrays 199 | byte lora_TXBUFF[128]; //buffer for packet to send 200 | byte lora_RXBUFF[128]; //buffer for packet to send 201 | //byte Variables 202 | byte lora_TXStart; //start of packet data in TXbuff 203 | byte lora_TXEnd; //end of packet data in TXbuff 204 | byte lora_FTXOK; //flag, set to 1 if TX OK 205 | byte lora_TXPacketType; //type number of packet to send 206 | byte lora_TXDestination; //destination address of packet to send 207 | byte lora_TXSource; //source address of packet received 208 | byte lora_FDeviceError; //flag, set to 1 if RFM98 device error 209 | byte lora_TXPacketL; //length of packet to send, includes source, destination and packet type. 210 | 211 | 212 | unsigned long lastTX = 0L; 213 | 214 | float BattVolts; 215 | 216 | // variables for smart beaconing 217 | float average_speed[5] = {0,0,0,0,0}, average_speed_final=0, max_speed=30, min_speed=0; 218 | float old_course = 0, new_course = 0; 219 | int point_avg_speed = 0, point_avg_course = 0; 220 | ulong min_time_to_nextTX=60000L; // minimum time period between TX = 60000ms = 60secs = 1min 221 | ulong nextTX=60000L; // preset time period between TX = 60000ms = 60secs = 1min 222 | #define ANGLE 60 // angle to send packet at smart beaconing 223 | #define ANGLE_AVGS 3 // angle averaging - x times 224 | float average_course[ANGLE_AVGS]; 225 | float avg_c_y, avg_c_x; 226 | 227 | #ifdef DEBUG 228 | // debug Variables 229 | String TxRoot="0"; 230 | float millis_angle[ANGLE_AVGS]; 231 | #endif 232 | 233 | #define TX_BASE91 // if BASE91 is set, packets will be sent compressed (in TRACKER-mode only) 234 | 235 | static const adc_atten_t atten = ADC_ATTEN_DB_6; 236 | static const adc_unit_t unit = ADC_UNIT_1; 237 | 238 | static void smartDelay(unsigned long); 239 | void recalcGPS(void); 240 | void sendpacket(void); 241 | void loraSend(byte, byte, byte, byte, byte, long, byte, float); 242 | void batt_read(void); 243 | void writedisplaytext(String, String, String, String, String, String, int); 244 | void setup_data(void); 245 | 246 | 247 | #ifdef DS18B20 248 | OneWire oneWire(ONE_WIRE_BUS); 249 | DallasTemperature sensors(&oneWire); 250 | #else 251 | #ifdef USE_BME280 252 | Adafruit_BME280 bme; // if BME is used 253 | #else 254 | DHTesp dht; // Initialize DHT sensor for normal 16mhz Arduino 255 | #endif 256 | #endif 257 | boolean tempsensoravailable=true; 258 | 259 | // SoftwareSerial ss(RXPin, TXPin); // The serial connection to the GPS device 260 | HardwareSerial ss(1); // TTGO has HW serial 261 | TinyGPSPlus gps; // The TinyGPS++ object 262 | #ifdef T_BEAM_V1_0 263 | AXP20X_Class axp; 264 | #endif 265 | 266 | // checkRX 267 | uint8_t buf[BG_RF95_MAX_MESSAGE_LEN]; 268 | uint8_t len = sizeof(buf); 269 | 270 | // Singleton instance of the radio driver 271 | 272 | BG_RF95 rf95(18, 26); // TTGO T-Beam has NSS @ Pin 18 and Interrupt IO @ Pin26 273 | 274 | // initialize OLED display 275 | #define OLED_RESET 4 // not used 276 | Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET); 277 | 278 | // +---------------------------------------------------------------------+// 279 | // + SETUP --------------------------------------------------------------+// 280 | // +---------------------------------------------------------------------+// 281 | 282 | void setup() 283 | { 284 | bool bme_status; 285 | 286 | for (int i=0;i 5000 && gps.charsProcessed() < 10) { 423 | writedisplaytext(" "+Tcall,"","Init:","ERROR!","No GPS data!","Please restart TTGO",0); 424 | Serial.println("LoRa-APRS / Init / GPS ERROR - no GPS data - please RESTART TTGO"); 425 | while (true) {blinker(1);} 426 | } 427 | writedisplaytext(" "+Tcall,"","Init:","Data from GPS OK!","","",250); 428 | Serial.println("LoRa-APRS / Init / Data from GPS OK!"); 429 | } else { 430 | writedisplaytext(" "+Tcall,"","Init:","GPS switched OFF!","","",250); 431 | Serial.println("LoRa-APRS / Init / GPS switched OFF!"); 432 | } 433 | 434 | #ifdef T_BEAM_V1_0 435 | writedisplaytext("LoRa-APRS","","Init:","ADC OK!","BAT: "+String(axp.getBattVoltage()/1000,1),"",250); 436 | Serial.print("LoRa-APRS / Init / ADC OK! / BAT: "); 437 | Serial.println(String(axp.getBattVoltage()/1000,1)); 438 | #else 439 | adc1_config_width(ADC_WIDTH_BIT_12); 440 | adc1_config_channel_atten(ADC1_CHANNEL_7,ADC_ATTEN_DB_6); 441 | writedisplaytext("LoRa-APRS","","Init:","ADC OK!","BAT: "+String(analogRead(35)*7.221/4096,1),"",250); 442 | Serial.print("LoRa-APRS / Init / ADC OK! / BAT: "); 443 | Serial.println(String(analogRead(35)*7.221/4096,1)); 444 | #endif 445 | 446 | rf95.setFrequency(433.775); 447 | rf95.setModemConfig(BG_RF95::Bw125Cr45Sf4096); // hard coded because of double definition 448 | rf95.setTxPower(5); 449 | 450 | #ifdef DS18B20 451 | sensors.begin(); 452 | #else 453 | #ifdef USE_BME280 454 | bme_status = bme.begin(0x76); 455 | if (!bme_status) 456 | { 457 | Serial.println("Could not find a valid BME280 sensor, check wiring!"); 458 | writedisplaytext("LoRa-APRS","","Init:","BME280 ERROR!","","",3000); 459 | tempsensoravailable = false; 460 | } 461 | #else 462 | dht.setup(DHTPIN,dht.AUTO_DETECT); // initialize DHT22 463 | #endif 464 | #endif 465 | delay(250); 466 | 467 | #ifdef DS18B20 468 | sensors.requestTemperatures(); // Send the command to get temperature readings 469 | temp = sensors.getTempCByIndex(0); // get temp from 1st (!) sensor only 470 | #else 471 | #ifdef USE_BME280 472 | pressure_offset = calc_pressure_offset(HEIGTH_PRESET); 473 | bme.takeForcedMeasurement(); 474 | temp = bme.readTemperature(); // bme Temperatur auslesen 475 | hum = bme.readHumidity(); 476 | pressure = bme.readPressure()/100 + pressure_offset; 477 | #else 478 | temp = dht.getTemperature(); 479 | hum = dht.getHumidity(); 480 | #endif 481 | #endif 482 | writedisplaytext("LoRa-APRS","","Init:","Temp OK!","TEMP: "+String(temp,1),"HUM: "+String(hum,1),250); 483 | Serial.print("LoRa-APRS / Init / Temp OK! Temp="); 484 | Serial.print(String(temp)); 485 | Serial.print(" Hum="); 486 | Serial.println(String(hum)); 487 | writedisplaytext("LoRa-APRS","","Init:","FINISHED OK!"," =:-) ","",250); 488 | Serial.println("LoRa-APRS / Init / FINISHED OK! / =:-)"); 489 | writedisplaytext("","","","","","",0); 490 | 491 | hum_temp_ctr = 0; 492 | } 493 | 494 | // +---------------------------------------------------------------------+// 495 | // + MAINLOOP -----------------------------------------------------------+// 496 | // +---------------------------------------------------------------------+// 497 | 498 | void loop() { 499 | if (digitalRead(BUTTON)==LOW) { 500 | ++button_ctr; 501 | if (button_ctr>=5) { 502 | switch(tracker_mode) { 503 | case TRACKER: 504 | tracker_mode = WX_TRACKER; 505 | writedisplaytext("LoRa-APRS","","New Mode","WX-TRACKER","","",500); 506 | Serial.println("LoRa-APRS / New Mode / WX-TRACKER"); 507 | blinker(2); 508 | break; 509 | case WX_TRACKER: 510 | tracker_mode = WX_MOVE; 511 | writedisplaytext("LoRa-APRS","","New Mode","WX-MOVING","","",500); 512 | Serial.println("LoRa-APRS / New Mode / WX-MOVING"); 513 | blinker(3); 514 | break; 515 | case WX_MOVE: 516 | tracker_mode = WX_FIXED; 517 | writedisplaytext("LoRa-APRS","","New Mode","WX-FIXED","","",500); 518 | Serial.println("LoRa-APRS / New Mode / WX-FIXED"); 519 | #ifdef T_BEAM_V1_0 520 | axp.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // switch OFF GPS at mode WX_FIXED 521 | #endif 522 | blinker(4); 523 | break; 524 | case WX_FIXED: 525 | default: 526 | tracker_mode = TRACKER; 527 | writedisplaytext("LoRa-APRS","","New Mode","TRACKER","","",500); 528 | Serial.println("LoRa-APRS / New Mode / TRACKER"); 529 | #ifdef T_BEAM_V1_0 530 | axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // switch on GPS in all modes except WX_FIXED 531 | #endif 532 | blinker(1); 533 | break; 534 | } 535 | prefs.begin("nvs", false); 536 | prefs.putChar("tracker_mode", (char) tracker_mode); 537 | prefs.end(); 538 | button_ctr=0; 539 | // ESP.restart(); 540 | } 541 | } else { 542 | button_ctr = 0; 543 | } 544 | 545 | if (hum_temp) { 546 | ++hum_temp_ctr; 547 | if (hum_temp_ctr>hum_temp_ctr_max) { 548 | hum_temp_ctr = 0; 549 | hum_temp=false; 550 | } 551 | #ifdef DS18B20 552 | sensors.requestTemperatures(); // Send the command to get temperature readings 553 | temp = sensors.getTempCByIndex(0); // get temp from 1st (!) sensor only 554 | #else 555 | #ifdef USE_BME280 556 | bme.takeForcedMeasurement(); 557 | temp = bme.readTemperature(); // bme Temperatur auslesen 558 | #else 559 | temp = dht.getTemperature(); 560 | #endif 561 | #endif 562 | } else { 563 | ++hum_temp_ctr; 564 | if (hum_temp_ctr>hum_temp_ctr_max) { 565 | hum_temp_ctr = 0; 566 | hum_temp=true; 567 | } 568 | #ifdef DS18B20 569 | hum = 0; 570 | #else 571 | #ifdef USE_BME280 572 | bme.takeForcedMeasurement(); 573 | hum = bme.readHumidity(); 574 | pressure = bme.readPressure()/100 + pressure_offset; 575 | #else 576 | hum = dht.getHumidity(); 577 | #endif 578 | #endif 579 | } 580 | 581 | if (tracker_mode != WX_FIXED) { 582 | while (ss.available() > 0) { 583 | gps.encode(ss.read()); 584 | } 585 | } 586 | 587 | if (rf95.waitAvailableTimeout(100)) { 588 | #ifdef SHOW_RX_PACKET // only show RX packets when activitated in config 589 | if (rf95.recvAPRS(lora_RXBUFF, &len)) { 590 | Serial.print("((RX)): "); 591 | InputString = ""; 592 | for ( int i=0 ; i < len ; i++) { 593 | InputString += (char) lora_RXBUFF[i]; 594 | } 595 | Serial.println(InputString); 596 | blinker(3); 597 | writedisplaytext(" ((RX))","",InputString,"","","",SHOW_RX_TIME); 598 | } 599 | #endif 600 | } 601 | 602 | if (tracker_mode != WX_FIXED) { 603 | LatShown = String(gps.location.lat(),5); 604 | LongShown = String(gps.location.lng(),5); 605 | 606 | average_speed[point_avg_speed] = gps.speed.kmph(); // calculate smart beaconing 607 | ++point_avg_speed; 608 | if (point_avg_speed>4) {point_avg_speed=0;} 609 | average_speed_final = (average_speed[0]+average_speed[1]+average_speed[2]+average_speed[3]+average_speed[4])/5; 610 | nextTX = (max_time_to_nextTX-min_time_to_nextTX)/(max_speed-min_speed)*(max_speed-average_speed_final)+min_time_to_nextTX; 611 | #ifdef DEBUG 612 | TxRoot="S"; 613 | #endif 614 | 615 | if (nextTX < min_time_to_nextTX) {nextTX=min_time_to_nextTX;} 616 | if (nextTX > max_time_to_nextTX) {nextTX=max_time_to_nextTX;} 617 | 618 | average_course[point_avg_course] = gps.course.deg(); // calculate smart beaconing course 619 | #ifdef DEBUG 620 | millis_angle[point_avg_course]=millis(); 621 | #endif 622 | ++point_avg_course; 623 | if (point_avg_course>(ANGLE_AVGS-1)) { 624 | point_avg_course=0; 625 | avg_c_y = 0; 626 | avg_c_x = 0; 627 | for (int i=0;i (360-ANGLE))) { 634 | if (abs(new_course-old_course-360)>=ANGLE) { 635 | nextTX = 0; 636 | // lastTX = min_time_to_nextTX 637 | #ifdef DEBUG 638 | TxRoot="W1"; 639 | for (int i=0;i<2;i++) 640 | { 641 | // TxRoot += " c:" + String(average_course[i]) + " t:" + String(millis_angle[i]); 642 | TxRoot += " " + String(millis_angle[i],2); 643 | } 644 | TxRoot = TxRoot + " new:" + String(new_course) + " old:" +String(old_course); 645 | #endif 646 | } 647 | } else { 648 | if ((old_course > (360-ANGLE)) && (new_course < ANGLE)) { 649 | if (abs(new_course-old_course+360)>=ANGLE) { 650 | nextTX = 0; 651 | #ifdef DEBUG 652 | TxRoot="W2"; 653 | for (int i=0;i<2;i++) 654 | { 655 | // TxRoot += " c:" + String(average_course[i]) + " t:" + String(millis_angle[i]); 656 | TxRoot += " " + String(millis_angle[i],2); 657 | } 658 | TxRoot = TxRoot + " new:" + String(new_course) + " old:" +String(old_course); 659 | #endif 660 | } 661 | } else { 662 | if (abs(new_course-old_course)>=ANGLE) { 663 | nextTX = 0; 664 | #ifdef DEBUG 665 | TxRoot="W3"; 666 | for (int i=0;i<2;i++) 667 | { 668 | // TxRoot += " c:" + String(average_course[i]) + " t:" + String(millis_angle[i]); 669 | TxRoot += " " + String(millis_angle[i],2); 670 | } 671 | TxRoot = TxRoot + " new:" + String(new_course) + " old:" +String(old_course); 672 | #endif 673 | } 674 | } 675 | } 676 | old_course = new_course; 677 | } 678 | } else { 679 | LatShown = LatFixed; 680 | LongShown = LongFixed; 681 | nextTX = max_time_to_nextTX; 682 | } 683 | 684 | batt_read(); 685 | 686 | if (button_ctr==2) { 687 | nextTX = 0; 688 | #ifdef DEBUG 689 | TxRoot="B"; 690 | #endif 691 | } 692 | 693 | if ((millis()-10) {outString += "0"; } 901 | tempf = abs(tempf); 902 | } else { // positive Werte erstellen 903 | if(tempf<100) {outString += "0"; } 904 | if(tempf<10) {outString += "0"; } 905 | } 906 | helper = String(tempf,0); 907 | helper.trim(); 908 | outString += helper; 909 | outString += "r...p...P...h"; 910 | if(hum<10) {outString += "0"; } 911 | if(hum<100) { 912 | helper = String(hum,0); 913 | helper.trim(); 914 | outString += helper; 915 | } else { 916 | // if humidity = 100% then send it as "00" as defined in APRS spec 917 | outString += "00"; 918 | } 919 | #ifdef USE_BME280 920 | outString += "b"; 921 | if(pressure<1000) {outString += "0"; } 922 | helper = String(pressure*10,0); 923 | helper.trim(); 924 | outString += helper; 925 | #else 926 | outString += "b....."; 927 | #endif 928 | outString += MY_COMMENT; 929 | #ifdef HW_COMMENT 930 | outString += (" --"); 931 | outString += " Batt="; 932 | outString += String(BattVolts,2); 933 | outString += ("V"); 934 | #endif 935 | break; 936 | case WX_TRACKER: 937 | if (wx) { 938 | #ifdef DS18B20 939 | sensors.requestTemperatures(); // Send the command to get temperature readings 940 | tempf = sensors.getTempFByIndex(0); // get temp from 1st (!) sensor only 941 | hum = 0; 942 | #else 943 | #ifdef USE_BME280 944 | bme.takeForcedMeasurement(); 945 | tempf = bme.readTemperature()*9/5+32; // bme Temperatur auslesen 946 | hum = bme.readHumidity(); 947 | pressure = bme.readPressure()/100 + pressure_offset; 948 | #else 949 | hum = dht.getHumidity(); 950 | tempf = dht.getTemperature()*9/5+32; 951 | #endif 952 | #endif 953 | #ifndef TX_BASE91 954 | for (i=0; i-10) {outString += "0"; } 997 | tempf = abs(tempf); 998 | } else { // positive Werte erstellen 999 | if(tempf<100) {outString += "0"; } 1000 | if(tempf<10) {outString += "0"; } 1001 | } 1002 | helper = String(tempf,0); 1003 | helper.trim(); 1004 | outString += helper; 1005 | outString += "r...p...P...h"; 1006 | if(hum<10) {outString += "0"; } 1007 | if(hum<100) { 1008 | helper = String(hum,0); 1009 | helper.trim(); 1010 | outString += helper; 1011 | } else { 1012 | // if humidity = 100% then send it as "00" as defined in APRS spec 1013 | outString += "00"; 1014 | } 1015 | #ifdef USE_BME280 1016 | outString += "b"; 1017 | if(pressure<1000) {outString += "0"; } 1018 | helper = String(pressure*10,0); 1019 | helper.trim(); 1020 | outString += helper; 1021 | #else 1022 | outString += "b....."; 1023 | #endif 1024 | outString += MY_COMMENT; 1025 | wx = !wx; 1026 | } else { 1027 | #ifndef TX_BASE91 1028 | for (i=0; i-10) {outString += "0"; } 1152 | tempf = abs(tempf); 1153 | } else { // positive Werte erstellen 1154 | if(tempf<100) {outString += "0"; } 1155 | if(tempf<10) {outString += "0"; } 1156 | } 1157 | helper = String(tempf,0); 1158 | helper.trim(); 1159 | outString += helper; 1160 | outString += "r...p...P...h"; 1161 | if(hum<10) {outString += "0"; } 1162 | if(hum<100) { 1163 | helper = String(hum,0); 1164 | helper.trim(); 1165 | outString += helper; 1166 | } else { 1167 | // if humidity = 100% then send it as "00" as defined in APRS spec 1168 | outString += "00"; 1169 | } 1170 | #ifdef USE_BME280 1171 | outString += "b"; 1172 | if(pressure<1000) {outString += "0"; } 1173 | helper = String(pressure*10,0); 1174 | helper.trim(); 1175 | outString += helper; 1176 | #else 1177 | outString += "b....."; 1178 | #endif 1179 | outString += MY_COMMENT; 1180 | break; 1181 | case TRACKER: 1182 | default: 1183 | #ifndef TX_BASE91 1184 | for (i=0; i","[","b","<","R"}; 1368 | String werte_weiter_symbol[2] = {"yes","no"}; 1369 | int8_t pos_in_string; 1370 | int8_t pos_ssid; 1371 | bool key_pressed = false; 1372 | int waiter; 1373 | int initial_waiter = 2000; 1374 | char aktueller_letter; 1375 | int8_t pos_letter; 1376 | String pfeile = "^"; 1377 | int8_t initial_ssid; 1378 | 1379 | 1380 | // set Tx Symbol und die normale SSID - gleich zu Beginn, falls man nur das Symbol ändern möchte 1381 | pos_ssid = 0; 1382 | while (true) { 1383 | TxSymbol = werte_TxSymbol_symbol[pos_ssid]; 1384 | blinker(pos_ssid+1); 1385 | writedisplaytext(" SETUP", " Symbol",werte_TxSymbol_text[pos_ssid], "", "PRESS KEY to select", "", 0); 1386 | waiter = millis(); 1387 | while (millis()<(waiter+1000+initial_waiter)) { 1388 | if (digitalRead(BUTTON)==LOW) { 1389 | key_pressed = true; 1390 | } 1391 | } 1392 | initial_waiter = 0; 1393 | if (key_pressed==true) { 1394 | key_pressed = false; 1395 | writedisplaytext(" SETUP", " Symbol",werte_TxSymbol_text[pos_ssid], "", "programmed", "", 2000); 1396 | break; 1397 | } 1398 | ++pos_ssid; 1399 | if (pos_ssid>=6) {pos_ssid=0;} 1400 | } 1401 | 1402 | // set normal SSID 1403 | initial_ssid = (int8_t) (Tcall.substring(7,9)).toInt(); 1404 | 1405 | pos_ssid = initial_ssid; 1406 | pfeile = " ^"; 1407 | key_pressed = false; 1408 | initial_waiter = 2000; 1409 | while (true) { 1410 | writedisplaytext(" SETUP", " normal SSID"," "+Tcall, pfeile, "PRESS KEY to select", "", 0); 1411 | waiter = millis(); 1412 | while (millis()<(waiter+1000+initial_waiter)) { 1413 | if (digitalRead(BUTTON)==LOW) { 1414 | key_pressed = true; 1415 | } 1416 | } 1417 | initial_waiter = 0; 1418 | if (key_pressed==true) { 1419 | key_pressed = false; 1420 | break; 1421 | } 1422 | ++pos_ssid; 1423 | if (pos_ssid>=16) {pos_ssid=0;} 1424 | Tcall = Tcall.substring(0,6)+"-"+werte_SSID[pos_ssid]; 1425 | } 1426 | 1427 | writedisplaytext(" SETUP", " SSID"," "+Tcall," ", "programmed", "", 2000); 1428 | 1429 | // fragen, ob es weiter gehen soll 1430 | pos_ssid = 0; 1431 | key_pressed = false; 1432 | initial_waiter = 2000; 1433 | while (true) { 1434 | // TxSymbol = werte_TxSymbol_symbol[pos_ssid]; 1435 | blinker(2-pos_ssid); 1436 | writedisplaytext(" SETUP", " stop it?"," "+werte_weiter_symbol[pos_ssid], "", "PRESS KEY to select", "", 0); 1437 | waiter = millis(); 1438 | while (millis()<(waiter+1000+initial_waiter)) { 1439 | if (digitalRead(BUTTON)==LOW) { 1440 | key_pressed = true; 1441 | } 1442 | } 1443 | initial_waiter = 0; 1444 | if (key_pressed==true) { 1445 | key_pressed = false; 1446 | writedisplaytext(" SETUP", " stop it?"," "+werte_weiter_symbol[pos_ssid], "", "selected", "", 2000); 1447 | break; 1448 | } 1449 | ++pos_ssid; 1450 | if (pos_ssid>=2) {pos_ssid=0;} 1451 | } 1452 | 1453 | if (pos_ssid != 0) { 1454 | // set callsign - one for both reports 1455 | 1456 | pos_in_string = 0; 1457 | key_pressed = false; 1458 | initial_waiter = 2000; 1459 | while (pos_in_string < 6) { 1460 | key_pressed = false; 1461 | aktueller_letter = (char) Tcall.charAt(pos_in_string);// ist Buchstabe holen 1462 | for (pos_letter=0;pos_letter<37;pos_letter++) { 1463 | if (aktueller_letter == werte_call[pos_letter]) { 1464 | break; 1465 | } 1466 | } 1467 | while (true) { 1468 | Tcall.setCharAt(pos_in_string, aktueller_letter); 1469 | writedisplaytext(" SETUP", " Call"," "+Tcall," "+pfeile, "PRESS KEY to select", "", 0); 1470 | waiter = millis(); 1471 | while (millis()<(waiter+1000+initial_waiter)) { 1472 | if (digitalRead(BUTTON)==LOW) { 1473 | key_pressed = true; 1474 | } 1475 | } 1476 | initial_waiter = 0; 1477 | if (key_pressed==true) { 1478 | key_pressed = false; 1479 | break; 1480 | } 1481 | // nächster Buchstabe 1482 | ++pos_letter; 1483 | if (pos_letter>=37) {pos_letter=0;} 1484 | aktueller_letter=werte_call[pos_letter]; 1485 | } 1486 | initial_waiter = 2000; 1487 | pfeile = " "+pfeile; 1488 | ++pos_in_string; 1489 | } 1490 | 1491 | writedisplaytext(" SETUP", " Call"," "+Tcall," ", "programmed", "", 2000); 1492 | 1493 | 1494 | // set WX SSID 1495 | initial_ssid = (int8_t) (wxTcall.substring(7,9)).toInt(); 1496 | 1497 | pos_ssid = initial_ssid; 1498 | key_pressed = false; 1499 | initial_waiter = 2000; 1500 | while (true) { 1501 | writedisplaytext(" SETUP", " WX SSID"," "+wxTcall, pfeile, "PRESS KEY to select", "", 0); 1502 | waiter = millis(); 1503 | while (millis()<(waiter+1000+initial_waiter)) { 1504 | if (digitalRead(BUTTON)==LOW) { 1505 | key_pressed = true; 1506 | } 1507 | } 1508 | initial_waiter = 0; 1509 | if (key_pressed==true) { 1510 | key_pressed = false; 1511 | break; 1512 | } 1513 | ++pos_ssid; 1514 | if (pos_ssid>=16) {pos_ssid=0;} 1515 | wxTcall = wxTcall.substring(0,6)+"-"+werte_SSID[pos_ssid]; 1516 | } 1517 | 1518 | writedisplaytext(" SETUP", " WX-Call"," "+wxTcall," ", "programmed", "", 2000); 1519 | 1520 | // set LONGITUDE 1521 | pfeile = "^"; 1522 | pos_in_string = 0; 1523 | key_pressed = false; 1524 | initial_waiter = 2000; 1525 | while (pos_in_string < 9) { 1526 | key_pressed = false; 1527 | aktueller_letter = (char) LongFixed.charAt(pos_in_string);// ist Buchstabe holen 1528 | for (pos_letter=0;pos_letter<14;pos_letter++) { 1529 | if (aktueller_letter == werte_latlon[pos_letter]) { 1530 | break; 1531 | } 1532 | } 1533 | while (true) { 1534 | LongFixed.setCharAt(pos_in_string, aktueller_letter); 1535 | writedisplaytext(" SETUP", " Longitude"," "+LongFixed," "+pfeile, "for fixed POS", "PRESS KEY to select", 0); 1536 | waiter = millis(); 1537 | while (millis()<(waiter+1000+initial_waiter)) { 1538 | if (digitalRead(BUTTON)==LOW) { 1539 | key_pressed = true; 1540 | } 1541 | } 1542 | initial_waiter = 0; 1543 | if (key_pressed==true) { 1544 | key_pressed = false; 1545 | break; 1546 | } 1547 | // nächster Buchstabe 1548 | ++pos_letter; 1549 | if (pos_letter>=14) {pos_letter=0;} 1550 | aktueller_letter=werte_latlon[pos_letter]; 1551 | } 1552 | initial_waiter = 2000; 1553 | pfeile = " "+pfeile; 1554 | ++pos_in_string; 1555 | if (pos_in_string == 5) { 1556 | ++pos_in_string; 1557 | pfeile = " "+pfeile; 1558 | } 1559 | } 1560 | 1561 | writedisplaytext(" SETUP", " Longitude"," "+LongFixed,"", "for fixed POS", "programmed", 2000); 1562 | 1563 | // set LATITUDE 1564 | pfeile = "^"; 1565 | pos_in_string = 0; 1566 | key_pressed = false; 1567 | initial_waiter = 2000; 1568 | while (pos_in_string < 8) { 1569 | key_pressed = false; 1570 | aktueller_letter = (char) LatFixed.charAt(pos_in_string);// ist Buchstabe holen 1571 | for (pos_letter=0;pos_letter<14;pos_letter++) { 1572 | if (aktueller_letter == werte_latlon[pos_letter]) { 1573 | break; 1574 | } 1575 | } 1576 | while (true) { 1577 | LatFixed.setCharAt(pos_in_string, aktueller_letter); 1578 | writedisplaytext(" SETUP", " Latitude"," "+LatFixed," "+pfeile, "for fixed POS", "PRESS KEY to select", 0); 1579 | waiter = millis(); 1580 | while (millis()<(waiter+1000+initial_waiter)) { 1581 | if (digitalRead(BUTTON)==LOW) { 1582 | key_pressed = true; 1583 | } 1584 | } 1585 | initial_waiter = 0; 1586 | if (key_pressed==true) { 1587 | key_pressed = false; 1588 | break; 1589 | } 1590 | // nächster Buchstabe 1591 | ++pos_letter; 1592 | if (pos_letter>=14) {pos_letter=0;} 1593 | aktueller_letter=werte_latlon[pos_letter]; 1594 | } 1595 | initial_waiter = 2000; 1596 | pfeile = " "+pfeile; 1597 | ++pos_in_string; 1598 | if (pos_in_string == 4) { 1599 | ++pos_in_string; 1600 | pfeile = " "+pfeile; 1601 | } 1602 | } 1603 | writedisplaytext(" SETUP", " Latitude"," "+LatFixed,"", "for fixed POS", "programmed", 2000); 1604 | 1605 | } 1606 | // write all values to NVRAM 1607 | prefs.begin("nvs", false); 1608 | prefs.putString("Tcall", Tcall); 1609 | prefs.putString("wxTcall", wxTcall); 1610 | prefs.putString("LatFixed", LatFixed); 1611 | prefs.putString("LongFixed", LongFixed); 1612 | prefs.putString("TxSymbol", TxSymbol); 1613 | prefs.end(); 1614 | writedisplaytext(" SETUP", "ALL DONE","", "stored in NVS", "", "", 2000); 1615 | } 1616 | /////////////////////////////////////////////////////////////////////////////////////// 1617 | void blinker(int counter) { 1618 | for (int i = 0; i < (counter-1); i++) { 1619 | digitalWrite(TXLED, HIGH); // turn blue LED ON 1620 | smartDelay(150); 1621 | digitalWrite(TXLED, LOW); // turn blue LED OFF 1622 | smartDelay(100); 1623 | } 1624 | digitalWrite(TXLED, HIGH); // turn blue LED ON 1625 | smartDelay(150); 1626 | digitalWrite(TXLED, LOW); // turn blue LED OFF 1627 | } 1628 | /////////////////////////////////////////////////////////////////////////////////////// 1629 | int calc_pressure_offset(int height) { 1630 | // 1631 | // A very simple method to calculate the offset for correcting the measured air pressure 1632 | // to the pressure at mean sea level (MSL). It is simplificated to "For each 8m change in height 1633 | // the pressure is changing by 1hPa." 1634 | // The exact method is described at 1635 | // https://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Internationale_H%C3%B6henformel 1636 | // 1637 | int offset = round(height / 8); 1638 | return(offset); 1639 | } 1640 | /////////////////////////////////////////////////////////////////////////////////////// 1641 | -------------------------------------------------------------------------------- /src/TTGO_T-Beam_LoRa_APRS_config.h: -------------------------------------------------------------------------------- 1 | // Tracker for LoRA APRS Header for configuration 2 | // 3 | // TTGO T-Beam includes GPS module + optional DHT22 (not yet DONE) 4 | // 5 | // can be used as tracker only, tracker plus weather reports (temperature and humidity) or weather reports station only 6 | // 7 | // updated from OE1ACM sketch by OE3CJB to enable WX data to be sent via LoRa APRS. 8 | // one package is with position and battery voltage 9 | // the next is with weather data in APRS format 10 | // 11 | // licensed under CC BY-NC-SA 12 | // 13 | // version: V1.3 14 | // last update: 27.08.2020 15 | // change history 16 | // symbol RV added 17 | // compressed packets in tracker mode (base91) 18 | // 19 | // version: V1.1beta 20 | // last update: 22.11.2019 21 | // 22 | // change history 23 | // version V1.0 24 | // added HW Version V1.0 support 25 | // added presetting in the header TTGO...config.h to prevent long initial setup at first boot up 26 | // added "SPACE" to allowed letters for callsign for shorter callsigns - has to be added at the end 27 | // added smart beaconing 28 | // 29 | // version V1.2 30 | // first released version 31 | 32 | // SET HW version 33 | #define T_BEAM_V1_0 // use this for newer Boards AKA Rev1 (second board release) 34 | // #define T_BEAM_V0_7 // use this for older Boards AKA Rev0.x (first board release) 35 | 36 | // SET temperature sensor type 37 | // #define DS18B20 // use this if you use DS18B20, default is DHT22 38 | // #define USE_BME280 // use this if you use BME280,m default is DHT22 39 | // #define HEIGTH_PRESET 234 // if you use BME280, the heigth of your location above mean sea level in meters 40 | 41 | // USER DATA - USE THESE LINES TO MODIFY YOUR PREFERENCES 42 | // IF NOT CHANGED you have to go through the configuration routine at first boot up of the TTGO T-Beam 43 | 44 | // #define DONT_USE_FLASH_MEMORY // uncomment if you don't want to use Flashmemory - instead data below must be corrected 45 | #define TRACKERMODE 1 // preset MODE here, if flash not used >> "0"=TRACKER, "1"=WX_TRACKER, "2"=WX_MOVE, "3"=WX_FIXED 46 | #define CALLSIGN "XX9XXX-11" // enter your callsign here - less then 6 letter callsigns please add "spaces" so total length is 6 (without SSID) 47 | #define WX_CALLSIGN "XX9XXX-11" // use same callsign but you can use different SSID 48 | #define LONGITUDE_PRESET "01539.85E" // please in APRS notation DDDMM.mmE or DDDMM.mmW 49 | #define LATIDUDE_PRESET "4813.62N" // please in APRS notation DDMM.mmN or DDMM.mmS 50 | #define APRS_SYMBOL ">" // other symbols are 51 | // "_" => Weather Station 52 | // ">" => CAR 53 | // "[" => RUNNER 54 | // "b" => BICYCLE 55 | // "<" => MOTORCYCLE 56 | // "R" => Recreation Vehicle 57 | // #define HW_COMMENT // send Alt und Battery Voltage, UNcomment if you want to send it 58 | #define MY_COMMENT "" // add your coment here - if empty then no comment is sent 59 | // #define MY_COMMENT "TTGO by OE3CJB" // add your coment here - if empty then no comment is sent 60 | 61 | // TRANSMIT INTERVAL 62 | unsigned long max_time_to_nextTX = 180000L; // set here MAXIMUM time in ms(!) for smart beaconing - minimum time is always 1 min = 60 secs = 60000L !!! 63 | // when entering 60000L intervall is fixed to 1 min 64 | 65 | // show RX values 66 | // #define SHOW_RX_PACKET // uncomment to show received LoRa APS packets for the time given below 67 | #define SHOW_RX_TIME 5000 // show RX packet for milliseconds (5000 = 5secs) 68 | --------------------------------------------------------------------------------