├── .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 |
11 | - RadioHead
12 | - TinyGPSPlus
13 | - DHT sensor library for ESPx
14 | - Adafruit SSD1306
15 | - Adafruit GFX Library
16 | - Adafruit Unified Sensor
17 | - AXP202X_Library
18 | - OneWire
19 | - DallasTemperature
20 |
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()APRS:!";
893 | outString += LatFixed;
894 | outString += wxTable;
895 | outString += LongFixed;
896 | outString += wxSymbol;
897 | outString += ".../...g...t";
898 | if (tempf < 0) { // negative Werte erstellen
899 | outString += "-";
900 | if(tempf>-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; iAPRS:!";
961 | if(Tlat<10) {outString += "0"; }
962 | outString += String(Lat,2);
963 | outString += Ns;
964 | outString += wxTable;
965 | if(Tlon<100) {outString += "0"; }
966 | if(Tlon<10) {outString += "0"; }
967 | outString += String(Lon,2);
968 | outString += Ew;
969 | outString += wxSymbol;
970 | #else
971 | for (i=0; iAPRS:!/";
978 | ax25_base91enc(helper_base91, 4, aprs_lat);
979 | for (i=0; i<4; i++) {
980 | outString += helper_base91[i];
981 | }
982 | ax25_base91enc(helper_base91, 4, aprs_lon);
983 | for (i=0; i<4; i++) {
984 | outString += helper_base91[i];
985 | }
986 | outString += wxSymbol;
987 | ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse/4 );
988 | outString += helper_base91[0];
989 | ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed)/0.07696));
990 | outString += helper_base91[0];
991 | outString += "\x48";
992 | #endif
993 | outString += ".../...g...t";
994 | if (tempf < 0) { // negative Werte erstellen
995 | outString += "-";
996 | if(tempf>-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; iAPRS:!";
1035 | if(Tlat<10) {outString += "0"; }
1036 | outString += String(Lat,2);
1037 | outString += Ns;
1038 | outString += sTable;
1039 | if(Tlon<100) {outString += "0"; }
1040 | if(Tlon<10) {outString += "0"; }
1041 | outString += String(Lon,2);
1042 | outString += Ew;
1043 | outString += TxSymbol;
1044 | if(Tcourse<100) {outString += "0"; }
1045 | if(Tcourse<10) {outString += "0"; }
1046 | Coursex = String(Tcourse,0);
1047 | Coursex.replace(" ","");
1048 | outString += Coursex;
1049 | outString += "/";
1050 | if(Tspeed<100) {outString += "0"; }
1051 | if(Tspeed<10) {outString += "0"; }
1052 | Speedx = String(Tspeed,0);
1053 | Speedx.replace(" ","");
1054 | outString += Speedx;
1055 | #else
1056 | for (i=0; iAPRS:!/";
1063 | ax25_base91enc(helper_base91, 4, aprs_lat);
1064 | for (i=0; i<4; i++) {
1065 | outString += helper_base91[i];
1066 | }
1067 | ax25_base91enc(helper_base91, 4, aprs_lon);
1068 | for (i=0; i<4; i++) {
1069 | outString += helper_base91[i];
1070 | }
1071 | outString += TxSymbol;
1072 | ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse/4 );
1073 | outString += helper_base91[0];
1074 | ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed)/0.07696));
1075 | outString += helper_base91[0];
1076 | outString += "\x48";
1077 | #endif
1078 |
1079 | #ifdef HW_COMMENT
1080 | outString += "/A=";
1081 | outString += Altx;
1082 | outString += " Batt=";
1083 | outString += String(BattVolts,2);
1084 | outString += ("V");
1085 | #endif
1086 | outString += MY_COMMENT;
1087 | wx = !wx;
1088 | }
1089 | break;
1090 | case WX_MOVE:
1091 | #ifdef DS18B20
1092 | sensors.requestTemperatures(); // Send the command to get temperature readings
1093 | tempf = sensors.getTempFByIndex(0); // get temp from 1st (!) sensor only
1094 | hum = 0;
1095 | #else
1096 | #ifdef USE_BME280
1097 | bme.takeForcedMeasurement();
1098 | tempf = bme.readTemperature()*9/5+32; // bme Temperatur auslesen
1099 | hum = bme.readHumidity();
1100 | pressure = bme.readPressure()/100 + pressure_offset;
1101 | #else
1102 | hum = dht.getHumidity();
1103 | tempf = dht.getTemperature()*9/5+32;
1104 | #endif
1105 | #endif
1106 |
1107 |
1108 | #ifndef TX_BASE91
1109 | for (i=0; iAPRS:!";
1116 | if(Tlat<10) {outString += "0"; }
1117 | outString += String(Lat,2);
1118 | outString += Ns;
1119 | outString += wxTable;
1120 | if(Tlon<100) {outString += "0"; }
1121 | if(Tlon<10) {outString += "0"; }
1122 | outString += String(Lon,2);
1123 | outString += Ew;
1124 | outString += wxSymbol;
1125 | #else
1126 | for (i=0; iAPRS:!/";
1133 | ax25_base91enc(helper_base91, 4, aprs_lat);
1134 | for (i=0; i<4; i++) {
1135 | outString += helper_base91[i];
1136 | }
1137 | ax25_base91enc(helper_base91, 4, aprs_lon);
1138 | for (i=0; i<4; i++) {
1139 | outString += helper_base91[i];
1140 | }
1141 | outString += wxSymbol;
1142 | ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse/4 );
1143 | outString += helper_base91[0];
1144 | ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed)/0.07696));
1145 | outString += helper_base91[0];
1146 | outString += "\x48";
1147 | #endif
1148 | outString += ".../...g...t";
1149 | if (tempf < 0) { // negative Werte erstellen
1150 | outString += "-";
1151 | if(tempf>-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; iAPRS:!";
1191 | if(Tlat<10) {outString += "0"; }
1192 | outString += String(Lat,2);
1193 | outString += Ns;
1194 | outString += sTable;
1195 | if(Tlon<100) {outString += "0"; }
1196 | if(Tlon<10) {outString += "0"; }
1197 | outString += String(Lon,2);
1198 | outString += Ew;
1199 | outString += TxSymbol;
1200 | if(Tcourse<100) {outString += "0"; }
1201 | if(Tcourse<10) {outString += "0"; }
1202 | Coursex = String(Tcourse,0);
1203 | Coursex.replace(" ","");
1204 | outString += Coursex;
1205 | outString += "/";
1206 | if(Tspeed<100) {outString += "0"; }
1207 | if(Tspeed<10) {outString += "0"; }
1208 | Speedx = String(Tspeed,0);
1209 | Speedx.replace(" ","");
1210 | outString += Speedx;
1211 | #ifdef HW_COMMENT
1212 | outString += "/A=";
1213 | outString += Altx;
1214 | outString += " Batt=";
1215 | outString += String(BattVolts,2);
1216 | outString += ("V");
1217 | #endif
1218 | outString += MY_COMMENT;
1219 | #ifdef DEBUG
1220 | outString += (" Debug: ");
1221 | outString += TxRoot;
1222 | #endif
1223 | #else
1224 | for (i=0; iAPRS:!/";
1231 | ax25_base91enc(helper_base91, 4, aprs_lat);
1232 | for (i=0; i<4; i++) {
1233 | outString += helper_base91[i];
1234 | }
1235 | ax25_base91enc(helper_base91, 4, aprs_lon);
1236 | for (i=0; i<4; i++) {
1237 | outString += helper_base91[i];
1238 | }
1239 | outString += TxSymbol;
1240 | ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse/4 );
1241 | outString += helper_base91[0];
1242 | ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed)/0.07696));
1243 | outString += helper_base91[0];
1244 | outString += "\x48";
1245 | #ifdef HW_COMMENT
1246 | outString += "/A=";
1247 | outString += Altx;
1248 | outString += " Batt=";
1249 | outString += String(BattVolts,2);
1250 | outString += ("V");
1251 | #endif
1252 | outString += MY_COMMENT;
1253 | #endif
1254 | Serial.print("outString=");
1255 | // Speedx = String(Tspeed,0);
1256 | // Speedx.replace(" ","");
1257 | Serial.println(outString);
1258 | // Serial.println("=");
1259 | break;
1260 | }
1261 | }
1262 |
1263 | /////////////////////////////////////////////////////////////////////////////////////////
1264 | void sendpacket()
1265 | {
1266 |
1267 | batt_read();
1268 | Outputstring = "";
1269 |
1270 | switch(tracker_mode) {
1271 | case WX_FIXED:
1272 | recalcGPS(); //
1273 | Outputstring =outString;
1274 | loraSend(lora_TXStart, lora_TXEnd, 60, 255, 1, 10, TXdbmW, TXFREQ); //send the packet, data is in TXbuff from lora_TXStart to lora_TXEnd
1275 | break;
1276 | case TRACKER:
1277 | case WX_TRACKER:
1278 | case WX_MOVE:
1279 | default:
1280 | if ( gps.location.isValid() || gps.location.isUpdated() ) {
1281 | recalcGPS(); //
1282 | Outputstring =outString;
1283 | loraSend(lora_TXStart, lora_TXEnd, 60, 255, 1, 10, TXdbmW, TXFREQ); //send the packet, data is in TXbuff from lora_TXStart to lora_TXEnd
1284 | } else {
1285 | Outputstring = (Tcall);
1286 | Outputstring += " No GPS-Fix";
1287 | Outputstring += " Batt=";
1288 | Outputstring += String(BattVolts,2);
1289 | Outputstring += ("V ");
1290 | loraSend(lora_TXStart, lora_TXEnd, 60, 255, 1, 10, 5, TXFREQ); //send the packet, data is in TXbuff from lora_TXStart to lora_TXEnd
1291 | }
1292 | break;
1293 | }
1294 | }
1295 |
1296 | ///////////////////////////////////////////////////////////////////////////////////////
1297 | void loraSend(byte lora_LTXStart, byte lora_LTXEnd, byte lora_LTXPacketType, byte lora_LTXDestination, byte lora_LTXSource, long lora_LTXTimeout, byte lora_LTXPower, float lora_FREQ)
1298 | {
1299 | byte i;
1300 | byte ltemp;
1301 |
1302 | if (rf95.waitAvailableTimeout(100)) {
1303 | if (rf95.recvAPRS(buf, &len)) {
1304 | }
1305 | }
1306 |
1307 | // time of last TX
1308 | lastTX = millis();
1309 |
1310 | ltemp = Outputstring.length();
1311 | for (i = 0; i <= ltemp; i++)
1312 | {
1313 | lora_TXBUFF[i] = Outputstring.charAt(i);
1314 | }
1315 |
1316 | i--;
1317 | lora_TXEnd = i;
1318 | lora_TXBUFF[i] ='\0';
1319 |
1320 | // digitalWrite(PLED1, HIGH); //LED on during packet
1321 |
1322 | rf95.setModemConfig(BG_RF95::Bw125Cr45Sf4096);
1323 | rf95.setFrequency(lora_FREQ);
1324 | rf95.setTxPower(lora_LTXPower);
1325 | rf95.sendAPRS(lora_TXBUFF, Outputstring.length());
1326 | rf95.waitPacketSent();
1327 | }
1328 | ///////////////////////////////////////////////////////////////////////////////////////
1329 | void batt_read()
1330 | {
1331 | float BattRead = analogRead(35)*7.221;
1332 | #ifdef T_BEAM_V1_0
1333 | BattVolts = axp.getBattVoltage()/1000;
1334 | #else
1335 | BattVolts = (BattRead / 4096);
1336 | #endif
1337 | }
1338 |
1339 | ///////////////////////////////////////////////////////////////////////////////////////
1340 | void writedisplaytext(String HeaderTxt, String Line1, String Line2, String Line3, String Line4, String Line5, int warten) {
1341 | display.clearDisplay();
1342 | display.setTextColor(WHITE);
1343 | display.setTextSize(2);
1344 | display.setCursor(0,0);
1345 | display.println(HeaderTxt);
1346 | display.setTextSize(1);
1347 | display.setCursor(0,16);
1348 | display.println(Line1);
1349 | display.setCursor(0,26);
1350 | display.println(Line2);
1351 | display.setCursor(0,36);
1352 | display.println(Line3);
1353 | display.setCursor(0,46);
1354 | display.println(Line4);
1355 | display.setCursor(0,56);
1356 | display.println(Line5);
1357 | display.display();
1358 | smartDelay(warten);
1359 | }
1360 |
1361 | ///////////////////////////////////////////////////////////////////////////////////////
1362 | void setup_data(void) {
1363 | char werte_call[37] = {' ','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','1','2','3','4','5','6','7','8','9','0'};
1364 | String werte_SSID[16] = {"0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15"};
1365 | char werte_latlon[14] = {'0','1','2','3','4','5','6','7','8','9','N','S','E','W'};
1366 | String werte_TxSymbol_text[6] = {"WX Station"," Car"," Person"," Bicycle","Motorcycle"," RV"};
1367 | String werte_TxSymbol_symbol[6] = {"_",">","[","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 |
--------------------------------------------------------------------------------