Adafruit 32u4 LoRa with Display¶
This tutorial is made to showcase the use of Adafruit 32u4 board to create a LoRaWAN enabled sensor node with a display and a case. In the following example, a temperature and humidity sensor was used with the Adafruit 32u4 board to create this tutorial.
Hardware¶
To build this sensor node we have used following hardware components:
- Adafruit Feather 32u4 LoRa module
- Grove - DHT-22 Temperature & Humidity Sensor
- LED Display
- Breadboard
- Battery
- Resistor: 4.7k to 10k Ohm
- 3d-Printed case
Microcontroller¶
The Adafruit Feather 32u4 LoRa module is operated by the 8bit ATmega32u4 microcontroller running at 8MHz. It has 32 KB flash memory (to store the program code) and 2 KB of RAM (to store variables, status information, and buffers). The operating voltage of the board is 3.3V (this is important when attaching sensors and other peripherals; they also must operate on 3.3V). The board offers 20 general purpose digital input/output pins (20 GPIOs) with 10 analog input pins (with 12bit analog digital converters (ADC)), one serial port (programmable Universal Asynchronous Receiver and Transmitter, UART), one I2C port, one SPI port, one USB port. The board comes with an embedded Lithium polymer battery management chip and status indicator led, which allows to directly connect a 3.7V LiPo rechargeable battery that will be automatically recharged when the board is powered over its USB connector. The Adafruit Feather 32u4 LoRa board is available in German shops from around 37 € to 45 €.
The LoRa transmitter and receiver is encapsulated within an RFM95 module from the company HopeRF. This module uses the LoRa chip SX1276 from the company Semtech and is dedicated to the 868 MHz frequency band. The RFM95 module is connected via SPI interface to the microcontroller. Most of the required connections of the LoRa transceiver pins with the microcontroller are already built-in on the Adafruit Feather 32u4 LoRa board. However, Digital Pin 6 of the microcontroller must be connected to DIO1 of the LoRa transceiver module in addition using a simple wire. Since the module only implements the LoRa physical layer, the LoRaWAN protocol stack must be implemented in software on the microcontroller. We are using the Arduino library LMIC for that purpose (see below). The implemented LoRaWAN functionality is compatible with LoRaWAN Class A/C.
Sensor¶
We have attached a DHT22 sensor to the microcontroller board, which measures air temperature and humidity. The minimal time interval between two measurements is 2 seconds. All data transfers between the DHT22 and the microcontroller use a single digital line. The sensor data pin is attached to a GPIO pin (here: Digital Pin 6) of the microcontroller. In addition, a so-called pull-up resistor of 4.7k to 10k Ohm must be connected between the data line and VCC (+3.3V). The DHT22 datasheet provides more technical details about the DHT22 Sensor. A tutorial on how to use the DHT22 sensor with Arduino microcontrollers is provided here. The sensor is available in German shops for around 4 € to 10 €.
Display / Beeper¶
On top of the microcontroller board we have attached an Adafruit Display Wing with a 4 digit 14 segments LED display. It can show 0-4 numbers or letters (upper case and lower case). The display controller is using the I2C protocol and the I2C pins SDA and SCL are directly connected to the Adafruit Feather via the Wing connectors. The Wing is using the default I2C address (0x70). Also a 3.3V beeper is installed that is used to indicate that a new message was received and is now being displayed. The ‘+’ pin of the beeper has to be connected to Digital Pin 12 and the ‘-‘ pin to GND. The display and the beeper can be used to notify a user with (very) short messages. The reason why we have included this is mostly to experiment with and to demonstrate the downlink capabilities of LoRaWAN. When a downlink message has been queued it will be transmitted to the node right after it has transmitted the next data packet (uplink data). Hence, it depends on the transmission time period how long it can take unless the node receives and displays a downlink message.
Case¶
The case was 3D printed using the design files provided by Adafruit. The case consists of three parts. Part 1 is the main enclosure (it does not have a switch holder or tabs, the design file is feather-case.stl). Part 2 is the battery holder (with a slide switch holder, the design file is feather-bat-switch.stl). Part 3 is the case topper (with a cutout for the Adafruit Feather Wing, the design file is feather-top-wing.stl). All design files can be downloaded from Thingiverse.
We have ordered the three parts from an online 3D printing service. The quality of the delivered parts was generally ok, but not good enough for snapping the three parts firmly together. It is not clear yet whether this is a problem of the design files or of the printing service. We used two rubber bands In order to fix the three parts together.
Once all these connection are made, the board is connected with a computer using a USB cable. Further, steps of software part needs to be followed. But, before that we need to register a new device on the service that we are using.
Software¶
The sensor node has been programmed using the Arduino IDE. Please note, that in the Arduino framework a program is called a ‘Sketch’.
After the sketch has successfully established a connection to The Things Network it reports the air temperature, humidity, and the voltage of a (possibly) attached LiPo battery every 5 minutes. All three values are being encoded in two byte integer values each (in most significant byte order) and then sent as a 6 bytes data packet to the respective TTN application using LoRaWAN port 7. Please note, that LoRaWAN messages can be addressed to ports 1-255 (port 0 is reserved); these ports are similar to port numbers 0-65535 when using the Internet TCP/IP protocol. Voltage and humidity values are always greater or equal to 0, but the temperature value can also become negative. Negative values are represented as a two’s complement; this must be considered in the Payload Decoding Function used in The Things Network (see here).
In between two sensor readings the microcontroller is going into deep sleep mode to save battery power. We still have to run some tests to find out for how long the system can run using the 2000 mAh LiPo battery and the current version of the sketch. Showing a received message on the display draws a considerable amount of power and will shorten battery life significantly. Hence, when running on battery it is recommended to clear a displayed message soon by sending a simple space character (0x20). (Further optimizations would be possible, for example, not switching on the LED on the microcontroller board during LoRa data transmissions.)
The employed RFM95 LoRa module does not provide built-in support of the LoRaWAN protocol. Thus, it has to be implemented on the ATmega32u4 microcontroller. We use the IBM LMIC (LoraMAC-in-C) library for Arduino. Since the ATmega32u4 microcontroller only has 32 KB of flash memory and the LMIC library is taking most of it, there is only very limited code space left for the application dealing with the sensors (about 2 KB). Nevertheless, this is sufficient to query some sensors like in our example the DHT22.
Now download and run the Arduino Sketch for Adafruit32u4 LoRa with display sensor node file in the Arduino IDE. This code was created by merging the example code of both the sensors and the ttn-otaa example from the lmic library. Some required changes were made while merging the example codes. The user should change the network session key, app session key and device address in the code before compiling. These keys can be obtained from the TTN account as shown in the services section.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70.
static const u1_t PROGMEM APPEUI[8]={ 0x55, 0xC1, 0x00, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ 0xF6, 0xE2, 0x10, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
// The key shown here is the semtech default key.
static const u1_t PROGMEM APPKEY[16] = { 0xC2, 0x21, 0x2E, 0x7A, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
|
Following is the example code that can be used to measure the battery voltage of the sensor node:
1 2 3 4 5 6 7 8 9 10 11 | measuredvbat = analogRead(VBATPIN);
measuredvbat *= 2.0; // we divided by 2, so multiply back
measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
measuredvbat /= 1023.0; // convert to voltage
int16_vbat = round(measuredvbat * 100.0);
mydata[4] = (byte) (int16_vbat >> 8);
mydata[5] = (byte) (int16_vbat & 0x00FF);
SERIALDEBUG_PRINT(" \t");
SERIALDEBUG_PRINT("Battery Voltage: ");
SERIALDEBUG_PRINT(measuredvbat);
SERIALDEBUG_PRINTLN(" V");
|
Services¶
The services used for this sensor-node are:
- TheThingsNetwork service for LoRaWAN network service.
- TheThingsNetwork - OGC SensorWeb integration for uploading LoRaWAN sensor data into OGC infrastructure.
Registration of the sensor node with The Things Network (TTN)¶
The LoRaWAN protocol makes use of a number of different identifiers, addresses, keys, etc. These are required to unambiguously identify devices, applications, as well as to encrypt and decrypt messages. The names and meanings are nicely explained on a dedicated TTN web page.
The sketch given above connects the sensor node with The Things Network (TTN) using the Over-the-Air-Activation (OTAA) mode. In this mode, we use the three keys AppEUI, DevEUI, AppKey. The DevEUI should be delivered with the sensor node by the manufacturer, the other two keys are created using the TTN console. Each sensor node must be manually registered in the TTN console before it can be started. This assumes that you already have a TTN user account (which needs to be created otherwise). In the TTN console create a new device and enter the DevEUI number that was shipped with the Adafruit Feather LoRa board. Note that the shipped number only consists of 6 bytes while LoRaWAN requires an 8 bytes DevEUI. We simply add 0x00 0x00 in the middle of the 6 bytes provided. If you have lost the provided DevEUI you can also let the TTN console create a new one. After the registration of the device the respective keys (AppEUI, DevEUI, AppKey) can be copied from the TTN console and must be pasted into the the proper places in the source code of the sketch above. Please make sure that you choose for each of the three keys the correct byte ordering (DevEUI, AppEUI in LSB; AppKey in MSB). A detailed explanation of these steps is given here. Then the sketch can be compiled and uploaded to the Adafruit Feather 32u4 LoRa microcontroller. Note that the three constants (AppEUI, DevEUI, AppKey) must be changed in the source code for every new sensor node.
Using the OTAA mode has the advantage over the ABP (activation by personalization) mode that during connection the session keys are newly created which improves security. Another advantage is that the packet counter is automatically reset to 0 both in the node and in the TTN application.
TTN Payload Decoding¶
Everytime a data packet is received by a TTN application a dedicated Javascript function is being called (Payload Decoder Function). This function can be used to decode the received byte string and to create proper Javascript objects or values that can directly be read by humans when looking at the incoming data packet. This is also useful to format the data in a specific way that can then be forwarded to an external application (e.g. a sensor data platform like MyDevices or Thingspeak). Such a forwarding can be configured in the TTN console in the “Integrations” tab. TTN payload decoder for Adafruit32u4 LoRa with display sensor node given here checks if a packet was received on LoRaWAN port 7 and then assumes that it consists of the 6 bytes encoded as described above. It creates the three Javascript objects ‘temperature’, ‘humidity’, and ‘vbattery’. Each object has two fields: ‘value’ holds the value and ‘uom’ gives the unit of measure. The source code can simply be copied and pasted into the ‘decoder’ tab in the TTN console after having selected the application. Choose the option ‘Custom’ in the ‘Payload Format’ field. Note that when you also want to handle other sensor nodes sending packets on different LoRaWAN ports, then the Payload Decoder Function can be extended after the end of the if (port==7) {…} statement by adding else if (port==8) {…} else if (port==9) {…} etc.
The Things Network - OGC SensorWeb Integration¶
The presented Payload Decoder Function works also with the TTN-OGC SWE Integration for the 52° North Sensor Observation Service (SOS). This software component can be downloaded from this repository. It connects a TTN application with a running transactional Sensor Observation Service 2.0.0 (SOS). Data packets received from TTN are imported into the SOS. The SOS persistently stores sensor data from an arbitrary number of sensor nodes and can be queried for the most recent as well as for historic sensor data readings. The 52° North SOS comes with its own REST API and a nice web client allowing to browse the stored sensor data in a convenient way.
We are running an instance of the 52° North SOS and the TTN-OGC SWE Integration. The web client for this LoRaWAN sensor node can be accessed on this page. Here is a screenshot showing the webclient:
Sending a message to the Sensor Node (Downlink)¶
Using the TTN console we can send a message (i.e. a byte string) to the sensor node. In the TTN console application page click on the respective application. Then click on the ‘Devices’ tab and choose the proper sensor node (here: adafruit-feather-32u4-lora3). On the overview page scroll down to the ‘Downlink’ section. In the ‘Payload’ field enter 1 to 4 bytes. In order to show digits or letters on the LED display these must be ASCII encoded and have to be entered as hexadecimal numbers. When you click on the ‘Send’ button the message will be queued and the next time when the node sends its data packet (uplink) it will receive the message. The first 4 bytes will be shown on the display and the beeper indicates the reception of a new downlink message. In order to blank the display just send a one byte message with the value ‘20’ (hexadecimal for 32, which is the ASCII code for a space). When the node receives just a single blank character it will not produce a beeping sound. There is a nice web page offering online encoding of text to ASCII numbers in hexadecimal encoding. For example, in order to display the text ‘LoRa’, the four hexadecimal numbers 4C 6F 52 61 have to be entered in the Payload entry field.
Code files¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 | /*******************************************************************************
* Arduino Sketch for a LoRaWAN sensor node that is registered with
* 'The Things Network' (TTN) www.thethingsnetwork.org
*
* Author: Thomas H. Kolbe, thomas.kolbe@tum.de
* Version: 1.0.0
* Last update: 2018-12-09
*
* The sensor node is based on the Adafruit Feather LoRa microcontroller board
* with either the AVR ATmega32u4 or the ATSAMD21G18 ARM Cortex M0 microcontroller.
* See https://learn.adafruit.com/adafruit-feather-32u4-radio-with-lora-radio-module/
* or https://learn.adafruit.com/adafruit-feather-m0-radio-with-lora-radio-module/
* The sensor node uses a DHT22 sensor measuring air temperature and humidity.
* Also the voltage of an attached LiPo battery is monitored and sent as
* an observation. All three values are encoded as 2 byte integer values each.
* Hence, the total message payload is 6 bytes. Before the values are converted
* to integers they are multiplied by 100 to preserve 2 digits after the decimal
* point. Thus, the received values must be divided by 100 to obtain the measured
* values. The payload is sent every 300s to LoRaWAN port 7. The following
* Javascript function can be used as a payload decoding function in TTN:
*
* function Decoder(bytes, port) {
* // Decode an uplink message from a buffer
* // (array) of bytes to an object of fields.
* if (port==7) {
* var decoded = {
* "temperature": (bytes[0] << 8 | bytes[1]) / 100.0,
* "humidity": (bytes[2] << 8 | bytes[3]) / 100.0,
* "vbattery": (bytes[4] << 8 | bytes[5]) / 100.0
* };
* } else {
* var decoded = null;
* }
* return decoded;
* }
*
* In between two data transmissions the microcontroller board can go
* into sleep mode to reduce energy consumption for extended operation
* time when running on battery. Usage of the sleep mode must be
* explicitly configured below.
*
* Note, that the DHT22 data pin must be connected to Digital Pin 6 of the
* microcontroller board (for the Feather 32u4) or Digital Pin 12 (for
* the Feather M0). A resistor of 4.7k - 10k Ohm must be connected to
* the data pin and VCC (+3.3V).
*
* Digital Pin 5 (for the Feather 32u4) must be connected to DIO1 of the
* LoRa transceiver module using a simple wire.
*
* For this node we also attach an Adafruit Feather Wing with a four digit
* 14-segments LED display. The display controller is using I2C and the
* I2C pins SDA and SCL are directly connected to the Adafruit Feather
* via the Wing connectors. The wing is using the default I2C address
* (0x70). Any LoRaWAN downlink message sent to this node is shown on
* the display (only the first 4 characters). We treat each byte of the
* received payload as a character in ASCII code. Besides numbers and
* letters in upper and lower case also some special characters are
* supported. For further details on the Feather Display Wing see here:
* https://learn.adafruit.com/14-segment-alpha-numeric-led-featherwing
*
* In order to notify persons standing nearby that a new text was received
* we let the node beep a couple of times. Therefore, Digital Pin 12 (for
* the Feather 32u4) should be connected to the '+' port of a 3.3V buzzer
* module. The '-' port of the buzzer must be connected to GND.
* If a payload containing just a single space (character code 0x20) is
* received, the display will be blanked without emitting beeps.
*
* Note that if the LED display shows some text this will draw a
* significant amount of power. This will certainly reduce the operational
* duration when running on battery.
*
* The code is based on the Open Source library LMIC implementing the LoRaWAN
* protocol stack on top of a given LoRa transceiver module (here: RFM95 from
* HopeRF, which uses the Semtech SX1276 LoRa chip). The library is originally
* being developed by IBM and has been ported to the Arduino platform. See
* notes below from the original developers.
*
*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This uses OTAA (Over-the-air activation), where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
*
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in config.h.
*
*******************************************************************************/
// If the following line is uncommented, messages are being printed out to the
// serial connection for debugging purposes. When using the Arduino Integrated
// Development Environment (Arduino IDE), these messages are displayed in the
// Serial Monitor selecting the proper port and a baudrate of 115200.
// #define SERIALDEBUG
#ifdef SERIALDEBUG
#define SERIALDEBUG_PRINT(...) Serial.print(__VA_ARGS__)
#define SERIALDEBUG_PRINTLN(...) Serial.println(__VA_ARGS__)
#else
#define SERIALDEBUG_PRINT(...)
#define SERIALDEBUG_PRINTLN(...)
#endif
// If the following line is uncommented, the sensor node goes into sleep mode
// in between two data transmissions. Also the 2secs time between the
// initialization of the DHT22 sensor and the reading of the observations
// is spent in sleep mode.
// Note, that on the Adafruit Feather 32u4 LoRa board the Serial connection
// gets lost as soon as the board goes into sleep mode, and it will not be
// established again. Thus, the definition of SERIALDEBUG should be commented
// out above when using sleep mode with this board.
#define SLEEPMODE
#ifdef SLEEPMODE
#include <Adafruit_SleepyDog.h>
#endif
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <util/atomic.h>
#include <avr/power.h>
#include <DHT.h>
#ifdef __AVR_ATmega32U4__
#define DHTPIN 6 // Arduino Digital Pin which is connected to the DHT sensor for Feather 32u4.
#endif
#ifdef ARDUINO_SAMD_FEATHER_M0
#define DHTPIN 12 // Arduino Digital Pin which is connected to the DHT sensor for Feather M0.
#endif
#define DHTTYPE DHT22 // DHT 22 (AM2302)
DHT dht(DHTPIN, DHTTYPE); // create the sensor object
#ifdef __AVR_ATmega32U4__
#define VBATPIN A9 // battery voltage is measured from Analog Input A9 for Feather 32u4
#endif
#ifdef ARDUINO_SAMD_FEATHER_M0
#define VBATPIN A7 // battery voltage is measured from Analog Input A7 for Feather M0
#endif
#ifdef __AVR_ATmega32U4__
extern volatile unsigned long timer0_overflow_count;
#endif
#include <Wire.h>
#include <Adafruit_GFX.h>
#include "Adafruit_LEDBackpack.h"
Adafruit_AlphaNum4 alpha4 = Adafruit_AlphaNum4();
#define BUZZERPIN 12 // Arduino Digital Pin which is connected to the buzzer module
// The following three constants (AppEUI, DevEUI, AppKey) must be changed
// for every new sensor node. We are using the LoRaWAN OTAA mode (over the
// air activation). Each sensor node must be manually registered in the
// TTN console at https://console.thethingsnetwork.org before it can be
// started. In the TTN console create a new device and enter the DevEUI
// number that was shipped with the Adafruit Feather LoRa board. Note that
// the shipped number only consists of 6 bytes while LoRaWAN requires
// an 8 bytes DevEUI. We simply add 0x00 0x00 in the middle of the 6 bytes
// provided. If you have lost the provided DevEUI you can also let the
// TTN console create a new one. After the registration of the device the
// three values can be copied from the TTN console. A detailed explanation
// of these steps is given in
// https://learn.adafruit.com/the-things-network-for-feather?view=all
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, 0x70.
static const u1_t PROGMEM APPEUI[8]={ 0x55, 0xC1, 0x00, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8]={ 0xF6, 0xE2, 0x10, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
// The key shown here is the semtech default key.
static const u1_t PROGMEM APPKEY[16] = { 0xC2, 0x21, 0x2E, 0x7A, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };
void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
// The following array of bytes is a placeholder to contain the message payload
// which is transmitted to the LoRaWAN gateway. We are currently only using 6 bytes.
// Please make sure to extend the size of the array, if more sensors should be
// attached to the sensor node and the message payload becomes larger than 10 bytes.
static uint8_t mydata[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA};
static osjob_t sendjob;
// Schedule transmission every TX_INTERVAL seconds (might become longer due to duty
// cycle limitations). The total interval time is 2secs for the measurement
// plus 3secs for the LoRaWAN packet transmission plus SLEEP_TIME seconds
// plus SLEEP_TIME seconds (microcontroller in sleep mode)
const unsigned int TX_INTERVAL = 300; // overall cycle time (send one set of observations every 5mins)
const unsigned int TX_TIME = 22; // rough estimate of transmission time of a single packet
const unsigned int MEASURE_TIME = 2; // seconds measuring time
const unsigned int SLEEP_TIME = TX_INTERVAL - TX_TIME - MEASURE_TIME;
const unsigned int WAIT_TIME = TX_INTERVAL - TX_TIME - MEASURE_TIME;
// Pin mapping of the LoRa transceiver. Please make sure that DIO1 is connected
// to Arduino Digital Pin 6 using an external wire. DIO2 is left unconnected
// (it is only required, if FSK modulation instead of LoRa would be used).
#ifdef __AVR_ATmega32U4__
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {7, 5, LMIC_UNUSED_PIN}, // in the Feather 32u4 DIO0 is connected to Arduino Digital Pin 7
};
#endif
#ifdef ARDUINO_SAMD_FEATHER_M0
const lmic_pinmap lmic_pins = {
.nss = 8,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {3, 6, LMIC_UNUSED_PIN}, // in the Feather M0 DIO0 is connected to Arduino Digital Pin 3
};
#endif
void onEvent (ev_t ev) {
SERIALDEBUG_PRINT(os_getTime());
SERIALDEBUG_PRINT(": ");
switch(ev) {
case EV_SCAN_TIMEOUT:
SERIALDEBUG_PRINTLN(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
SERIALDEBUG_PRINTLN(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
SERIALDEBUG_PRINTLN(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
SERIALDEBUG_PRINTLN(F("EV_BEACON_TRACKED"));
break;
case EV_JOINING:
SERIALDEBUG_PRINTLN(F("EV_JOINING"));
break;
case EV_JOINED:
SERIALDEBUG_PRINTLN(F("EV_JOINED"));
// Disable link check validation (automatically enabled
// during join, but not supported by TTN at this time).
// LMIC_setLinkCheckMode(0);
break;
case EV_RFU1:
SERIALDEBUG_PRINTLN(F("EV_RFU1"));
break;
case EV_JOIN_FAILED:
SERIALDEBUG_PRINTLN(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
SERIALDEBUG_PRINTLN(F("EV_REJOIN_FAILED"));
break;
case EV_TXCOMPLETE:
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
SERIALDEBUG_PRINTLN(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
SERIALDEBUG_PRINTLN(F("Received ack"));
if (LMIC.dataLen) {
#ifdef SERIALDEBUG
SERIALDEBUG_PRINT(F("Received "));
SERIALDEBUG_PRINT(LMIC.dataLen);
SERIALDEBUG_PRINT(F(" bytes of payload: 0x"));
for (int i=0; i<LMIC.dataLen; i++) {
if (LMIC.frame[LMIC.dataBeg + i] < 0x10) {
SERIALDEBUG_PRINT(F("0"));
}
SERIALDEBUG_PRINT(LMIC.frame[LMIC.dataBeg + i], HEX);
}
SERIALDEBUG_PRINTLN();
#endif
// add your code to handle a received downlink data packet here
alpha4.clear();
for (int i=0; i<LMIC.dataLen && i<4; i++) {
alpha4.writeDigitAscii(i, LMIC.frame[LMIC.dataBeg + i]);
}
alpha4.writeDisplay();
if (!(LMIC.frame[LMIC.dataBeg]==' ' && LMIC.dataLen==1))
messagebeep();
}
#ifdef SLEEPMODE
// Schedule next transmission in 1ms second after the board returns from sleep mode
os_setTimedCallback(&sendjob, os_getTime()+ms2osticks(1), do_send);
SERIALDEBUG_PRINTLN("going to sleep now ... ");
// lmic library sleeps automatically after transmission has been completed
doSleep((uint32_t)SLEEP_TIME*1000);
/*
int sleepcycles = (int)SLEEP_TIME / 8;
int restsleep = (int)SLEEP_TIME % 8;
for(int i=0; i < sleepcycles; i++) {
Watchdog.sleep(8000); // maximum seems to be 8 seconds
SERIALDEBUG_PRINT('.');
}
if (restsleep) {
Watchdog.sleep(restsleep*1000);
SERIALDEBUG_PRINT('*');
}
SERIALDEBUG_PRINTLN("... woke up again");
#ifdef __AVR_ATmega32U4__
// The following statement is required to prevent that LMIC spends another
// couple of seconds busy waiting for some RX packets. This is only required
// when using SLEEPMODE, because during sleep mode the Arduino timer variables
// are not being incremented and LMIC job scheduling is based on this.
// timer0_overflow_count += 3E6;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
extern volatile unsigned long timer0_millis;
extern volatile unsigned long timer0_overflow_count;
timer0_millis += SLEEP_TIME*1000;
// timer0 uses a /64 prescaler and overflows every 256 timer ticks
timer0_overflow_count += microsecondsToClockCycles((uint32_t)SLEEP_TIME * 1000000) / (64 * 256);
}
#endif
*/
// We need to reset the duty cycle limits within the LMIC library.
// The reason is that in sleep mode the Arduino system timers millis and micros
// do not get incremented. However, LMIC monitors the adherence to the
// LoRaWAN duty cycle limitations using the system timers millis and micros.
// Since LMIC does not know that we have slept for a long time and duty
// cycle requirements in fact are met, we must reset the respective LMIC timers
// in order to prevent the library to wait for some extra time (which would
// not use sleep mode and, thus, would waste battery energy).
LMIC.bands[BAND_MILLI].avail = os_getTime();
LMIC.bands[BAND_CENTI].avail = os_getTime();
LMIC.bands[BAND_DECI].avail = os_getTime();
#else
// Schedule next transmission
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(WAIT_TIME), do_send);
#endif
break;
case EV_LOST_TSYNC:
SERIALDEBUG_PRINTLN(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
SERIALDEBUG_PRINTLN(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
SERIALDEBUG_PRINTLN(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
SERIALDEBUG_PRINTLN(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
SERIALDEBUG_PRINTLN(F("EV_LINK_ALIVE"));
break;
default:
SERIALDEBUG_PRINTLN(F("Unknown event"));
break;
}
}
void do_send(osjob_t* j){
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
SERIALDEBUG_PRINTLN(F("OP_TXRXPEND, not sending"));
} else {
// Prepare upstream data transmission at the next possible time.
float temperature, humidity, measuredvbat;
int16_t int16_temperature, int16_humidity, int16_vbat;
// Start a measurement to update the sensor's internal temperature & humidity reading.
// Note, that when fetching measurements from a DHT22 sensor, the reported
// values belong to the measurement BEFORE the current measurement.
// Therefore, in order to get current observations, we first perform a new measurement
// and wait 2 secs (which is the minimum time between two sensor observations for
// the DHT22) and then directly retrieve the observations again.
temperature = dht.readTemperature();
#ifdef SLEEPMODE
// Watchdog.sleep(MEASURE_TIME * 1000UL);
doSleep(MEASURE_TIME * 1000UL);
#else
delay(MEASURE_TIME * 1000UL);
#endif
// Now read the recently measured temperature (2 secs ago) as Celsius (the default)
temperature = dht.readTemperature();
// Read the recently measured humidity (2 secs ago)
humidity = dht.readHumidity();
// Check if any reads failed and exit early (to try again).
if (isnan(humidity) || isnan(temperature)) {
SERIALDEBUG_PRINTLN("Failed to read from DHT sensor!");
// blink the LED five times to indicate that the sensor values could not be read
for (int i=0; i<5; i++) {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on by making the voltage HIGH
delay(150);
digitalWrite(LED_BUILTIN, LOW); // turn the LED on by making the voltage HIGH
delay(150);
}
// ok, then wait for another period and try it again
os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
} else {
SERIALDEBUG_PRINT("Humidity: ");
SERIALDEBUG_PRINT(humidity);
SERIALDEBUG_PRINT(" %\t");
SERIALDEBUG_PRINT("Temperature: ");
SERIALDEBUG_PRINT(temperature);
SERIALDEBUG_PRINT(" °C ");
int16_temperature = round(100.0*temperature);
int16_humidity = round(100.0*humidity);
mydata[0] = (byte) (int16_temperature >> 8);
mydata[1] = (byte) (int16_temperature & 0x00FF);
mydata[2] = (byte) (int16_humidity >> 8);
mydata[3] = (byte) (int16_humidity & 0x00FF);
measuredvbat = analogRead(VBATPIN);
measuredvbat *= 2.0; // we divided by 2, so multiply back
measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
measuredvbat /= 1023.0; // convert to voltage
int16_vbat = round(measuredvbat * 100.0);
mydata[4] = (byte) (int16_vbat >> 8);
mydata[5] = (byte) (int16_vbat & 0x00FF);
SERIALDEBUG_PRINT(" \t");
SERIALDEBUG_PRINT("Battery Voltage: ");
SERIALDEBUG_PRINT(measuredvbat);
SERIALDEBUG_PRINTLN(" V");
// Send the 6 bytes payload to LoRaWAN port 7 and do not request an acknowledgement.
// The following call does not directly sends the data, but puts a "send job"
// in the job queue. This job eventually is performed in the call "os_runloop_once();"
// issued repeatedly in the "loop()" method below. After the transmission is
// complete, the EV_TXCOMPLETE event is signaled, which is handled in the
// event handler method "onEvent (ev_t ev)" above. In the EV_TXCOMPLETE branch
// then a new call to the "do_send(osjob_t* j)" method is being prepared for
// delayed execution with a waiting time of TX_INTERVAL seconds.
LMIC_setTxData2(7, mydata, 6, 0);
SERIALDEBUG_PRINTLN(F("Packet queued"));
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on by making the voltage HIGH
// Next TX is scheduled after TX_COMPLETE event.
}
}
}
void doSleep(uint32_t time) {
ADCSRA &= ~(1 << ADEN);
power_adc_disable();
while (time > 0) {
uint16_t slept;
if (time < 8000)
slept = Watchdog.sleep(time);
else
slept = Watchdog.sleep(8000);
// Update the millis() and micros() counters, so duty cycle
// calculations remain correct. This is a hack, fiddling with
// Arduino's internal variables, which is needed until
// https://github.com/arduino/Arduino/issues/5087 is fixed.
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
extern volatile unsigned long timer0_millis;
extern volatile unsigned long timer0_overflow_count;
timer0_millis += slept;
// timer0 uses a /64 prescaler and overflows every 256 timer ticks
timer0_overflow_count += microsecondsToClockCycles((uint32_t)slept * 1000) / (64 * 256);
}
if (slept >= time)
break;
time -= slept;
}
power_adc_enable();
ADCSRA |= (1 << ADEN);
}
void beep(bool longbeep) {
digitalWrite(BUZZERPIN, HIGH); // turn the BUZZER off by making the voltage LOW
if (longbeep)
delay(250);
else
delay(100);
digitalWrite(BUZZERPIN, LOW); // turn the BUZZER off by making the voltage LOW
delay(100);
}
void messagebeep() {
beep(false);
beep(true);
beep(false);
beep(false);
delay(200);
beep(false);
beep(true);
beep(false);
beep(false);
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
pinMode(BUZZERPIN, OUTPUT);
digitalWrite(BUZZERPIN, LOW); // turn the BUZZER off by making the voltage LOW
alpha4.begin(0x70); // pass in the I2C address of the display
alpha4.clear();
alpha4.writeDisplay();
alpha4.writeDigitAscii(0, 'T');
alpha4.writeDigitAscii(1, 'e');
alpha4.writeDigitAscii(2, 's');
alpha4.writeDigitAscii(3, 't');
alpha4.writeDisplay();
messagebeep();
delay(10000); // give enough time to open serial monitor (if needed) or to start uploading of a new sketch
alpha4.clear();
alpha4.writeDisplay();
#ifdef SERIALDEBUG
Serial.begin(115200);
// while (!Serial);
#endif
dht.begin(); // initialize DHT22 sensor
SERIALDEBUG_PRINTLN(F("Starting"));
#ifdef VCC_ENABLE
// For Pinoccio Scout boards
pinMode(VCC_ENABLE, OUTPUT);
digitalWrite(VCC_ENABLE, HIGH);
delay(1000);
#endif
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
#if defined(CFG_eu868)
// Set up the channels used by the Things Network, which corresponds
// to the defaults of most gateways. Without this, only three base
// channels from the LoRaWAN specification are used, which certainly
// works, so it is good for debugging, but can overload those
// frequencies, so be sure to configure the full frequency range of
// your network here (unless your network autoconfigures them).
// Setting up channels should happen after LMIC_setSession, as that
// configures the minimal channel set.
// NA-US channels 0-71 are configured automatically
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
// devices' ping slots. LMIC does not have an easy way to define set this
// frequency and support for class B is spotty and untested, so this
// frequency is not configured here.
#elif defined(CFG_us915)
// NA-US channels 0-71 are configured automatically
// but only one group of 8 should (a subband) should be active
// TTN recommends the second sub band, 1 in a zero based count.
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
LMIC_selectSubBand(1);
#endif
// Disable link check validation
// LMIC_setLinkCheckMode(0);
// TTN uses SF9 for its RX2 window.
LMIC.dn2Dr = DR_SF9;
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
LMIC_setDrTxpow(DR_SF9,14);
// Start job. This will initiate the repetitive sending of data packets,
// because after each data transmission, a delayed call to "do_send()"
// is being scheduled again.
do_send(&sendjob);
// The following settings should further reduce energy consumption. I have not
// tested them yet, they are taken from a post in the TTN forum. See
// https://www.thethingsnetwork.org/forum/t/adafruit-lora-feather-gateway/2440/50
/*
power_adc_disable();
power_usart0_disable();
power_twi_disable();
power_timer1_disable();
power_timer2_disable();
power_timer3_disable();
power_usart1_disable();
power_usb_disable();
USBCON |= (1 << FRZCLK);
PLLCSR &= ~(1 << PLLE);
USBCON &= ~(1 << USBE );
clock_prescale_set(clock_div_2);
*/
}
void loop() {
os_runloop_once();
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | function Decoder (bytes, port) {
var result = {};
var transformers = {};
if (port==7) {
transformers = {
'temperature': function transform (bytes) {
value=bytes[0]*256 + bytes[1];
if (value>=32768) value=value-65536;
return value/100.0;
},
'humidity': function transform (bytes) {
return (bytes[0]*256 + bytes[1])/100.0;
},
'vbattery': function transform (bytes) {
return (bytes[0]*256 + bytes[1])/100.0;
},
}
result['temperature'] = {
value: transformers['temperature'](bytes.slice(0, 2)),
uom: 'Celsius',
}
result['humidity'] = {
value: transformers['humidity'](bytes.slice(2, 4)),
uom: 'Percent',
}
result['vbattery'] = {
value: transformers['vbattery'](bytes.slice(4, 6)),
uom: 'Volt',
}
}
return result;
}
|
References¶
- Adafruit Feather 32u4 LoRa microntroller
- Adafruit Feather 32u4 LoRa tutorial
- IBM LMIC (LoraMAC-in-C) library for Arduino
- Using Adafruit Feather 32u4 RFM95 as an TTN Node - Stories - Labs
- TTN LoraWan Atmega32U4 based node – ABP version | Primal Cortex’s Weblog
- node-workshop/lora32u4.md at master · kersing/node-workshop · GitHub
- Got Adafruit Feather 32u4 LoRa Radio to work and here is how - End Devices (Nodes) - The Things Network
- Adafruit Feather as LoRaWAN node | Wolfgang Klenk
- LMiC on Adafruit Lora Feather successfully sends message to TTN and then halts with “Packet queued” - End Devices (Nodes) - The Things Network
- GitHub - marcuscbehrens/loralife
- GPS-Tracker - Stories - Labs
On battery saving / using the deep sleep mode
- Adafruit Feather 32u4 LoRa - long transmission time after deep sleep - End Devices (Nodes) - The Things Network and this
- Full Arduino Mini LoraWAN and 1.3uA Sleep Mode - End Devices (Nodes) - The Things Network
- Adding Method to Adjust hal_ticks Upon Waking Up from Sleep · Issue #109 · matthijskooijman/arduino-lmic
- minilora-test/minilora-test.ino at cbe686826bd84fac8381de47b5f5b02dd47c2ca0 · tkerby/minilora-test
- Arduino-LMIC library with low power mode - Mario Zwiers