KeHoSoftware
Published © GPL3+

M5STACK ATOM DTU LoRaWAN868 using Arduino IDE

M5STACK recently released the ATOM DTU LoRaWAN868. This tutorial teaches you how to connect it to The Things Network using Arduino IDE.

AdvancedFull instructions provided1 hour65
M5STACK ATOM DTU LoRaWAN868 using Arduino IDE

Things used in this project

Hardware components

Atom DTU LoRaWAN Kit 868MHz (ASR6501)
M5Stack Atom DTU LoRaWAN Kit 868MHz (ASR6501)
×1
PIR Sensor Human Body Infrared PIR Motion Sensor (AS312)
M5Stack PIR Sensor Human Body Infrared PIR Motion Sensor (AS312)
×1
M5STACK ENVIII
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Wiring Unit ENVIII and Unit PIR with M5STACK ATOM DTU LoRaWAN868

Simply connect both sensors with 2 GROVE cables to the ATOM DTU LoRaWAN868 system.

Code

M5ATOM_DTU_LoRaWan_TTN_OTAA_Node_01_V1_04.ino

Arduino
Please compile with the new Arduino IDE 2.0
/****************************************************************************
**                                                                         **
** Name:        M5ATOM_DTU_LoRaWan_TTN_OTAA_Node_01                        **
** Author:      Achim Kern                                                 **
** Interpreter: Arduino IDE 2.0.4 on MacBookPro                            **
** Licence:     Freeware                                                   **
** Function:    Main Program                                               **
**                                                                         **
** Notes:       based on idea from TTN, M5STACK and LCARS SmartHome        **
**              send sensor node data via LoRaWan to TTN V3                ** 
**                                                                         **
** History:                                                                **
**                                                                         **
** 1.00        - 01.02.2023 - initial release                              **
** 1.01        - 01.01.2023 - new code from M5_LoRaWan not DTU !           **
** 1.02        - 03.01.2023 - send and receive changes                     **
**                          - work on payload                              **
** 1.03        - 04.01.2023 - PIR sensor included                          **
** 1.04        - 06.01.2023 - hackster.io version                          **
**                                                                         **
****************************************************************************/
/*
 * Application and Version
 */
   const char* application  = "M5ATOM_DTU_LoRaWan_OTAA_Node_01";
   const char* aktu_version = "1.04";

/*
 * Introduction
 *
 * M5STACK recently released the ATOM DTU LoRaWAN868. 
 * This Source Code connects UNIT ENVIII, UNIT PIR to this system and
 * transmits all sensor data to The Things Network V3.
 *
 * Your LCARS SmartHome SensorNode will be able to measure your environment data 
 * (temperature, humidity and pressure) and intruder motions.
 * The LED in front of the ATOM will always inform you about the system status.
 *
 * LED WHITE - system is booting and joining TTN
 * LED BLUE - system has joined TTN and sending data
 * LED RED - intruder alert
 * LED GREEN - system actual in stand-by mode
 *
 * The Arduino Source Code is well documented and compiled with the new IDE 2.0.4.
 * https://docs.arduino.cc/software/ide-v2/tutorials/getting-started-ide-v2
 * Also you will see very detailed DEBUG messages about every single step.
 * We will and can easy include more sensors like CO2, PM10, Digital Light etc.
 *
 *  Please fill / edit your TTN device and application data in modul ACTOR_ATOM_DTU_LoRaWAN868 !

 /*
  * actual TTN payload decoder
  *
  * // we can change each end devive via port number
  *
  * function Decoder(bytes, port) 
  * {
  *   var decoded = {};
  * 
  *  if (port === 2) 
  *  {
  *    if (bytes.length==6)
  *    {
  *      var tmp = (bytes[0]<<8 | bytes[1]);
  *      var hum = (bytes[4]);
  *      var pre = (bytes[2]<<8 | bytes[3]);  
  *      var motion = (bytes[5]);
  *      decoded.env3_temperature = (tmp-5000)/100;
  *      decoded.env3_pressure = pre/10;
  *      decoded.env3_humidity = hum/2;
  *      decoded.pir_motion = motion;
  *       
	*      return decoded;
  *    }
  *    else
  *    {
	*      decoded.Status="JOIN - ATOM DTU LoRaWAN is online";
  *      return decoded;
  *    }
  *   }
  * }
  *
  */

/*
 * DEBUG_MODE:
 * if defined serial messages will be displayed
 */
   // Enable/disable DEBUG
   #define DEBUG_MODE

/*
 * M5 ATOM lib 
 */
   // https://github.com/m5stack/M5Atom
   #include "M5Atom.h"
      
/*
 * connected actors
 */

/*
 * M5STACK ATOM DTU LoRaWAN868  - data transmission actor
 *
 * Description:
 * ATOM DTU LoRaWAN868 is a LoRaWAN Programmable Data Transmission Unit (DTU) suitable for 868MHz frequency. 
 * The module adopts the ASR6501 scheme, which supports long-distance communication and has both ultra-low power consumption and high sensitivity. 
 * The module integrates the LoRaWAN protocol stack and adopts a serial communication interface (using AT command set for control). 
 * When used, it can be used as a collection node to access a large number of gateways for data collection and management. 
 * Integrate SMA external antenna interface to improve the communication quality and signal stability of the device. 
 * Unlike the DTU which generally only has the function of data transparent transmission, the ATOM DTU series adopts a more open architecture design. 
 * The controller ATOM LITE can modify the program at will according to the actual business. 
 * The whole machine reserves a variety of interfaces (RS485, I2C, custom interface) for user expansion, 
 * which is convenient for the rapid access of sensors and actuators. 
 * With its own guide rail clamping structure, it is perfectly embedded in various industrial control sites. 
 * A cost-effective solution for small data collection nodes.
 *
 * Wiki:
 * https://docs.m5stack.com/en/atom/atom_dtu_lorawan868
 * 
 * Product Features:
 * ASR6501
 * Operating frequency: 868MHz
 * Serial communication: UART 115200bps (AT command)
 * With super anti-interference ability, able to work normally in complex interference environment
 * RS485 communication interface (with 12V input interface, internal integrated DCDC step-down 5V)
 * Modbus Master/slave
 * Strong signal access capability
 * External antenna: SMA antenna interface
 * Grove expansion interface: -I2C x1 -Custom x1
 * Self-contained rail clamping 
 *
 */
   // Enable/disable actors if you want to
   #define ENABLE_ACTOR_ATOM_DTU_LoRaWAN868
   // Actors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_ACTOR_ATOM_DTU_LoRaWAN868
     // https://github.com/m5stack/M5-LoRaWAN
     #include "M5_LoRaWAN.h"
     M5_LoRaWAN LoRaWAN;
     String response;
     typedef enum { kError = 0, kConnecting, kConnected, kSending } DTUState_t;
     DTUState_t State = kConnecting;
     // Your TTN OTAA Mode access data 
     // fill in here your TTN device and application data !!!
     // we get the DEV EUI from the TTN console - and we have to set on our chip
     // TTN CONSOLE V3 DEV EUI
     char* DevEui="0000000000000000";
     // we get the APP EUI from the TTN console - and we have to set on our chip
     // TTN CONSOLE V3 APP EUI
     char* AppEui="0000000000000000";
     // we get the APP KEY from the TTN console - and we have to set on our chip
     // TTN CONSOLE V3 APP KEY
     char* AppKey="00000000000000000000000000000000";
   #endif   

/*
 * connected sensors
 */

/*
 * M5STACK UNIT ENVIII - environment sensor
 *
 * Description: 
 * ENV III is an environmental sensor that integrates SHT30 and QMP6988 internally
 * to detect temperature, humidity, and atmospheric pressure data. 
 * SHT30 is a high-precision and low-power digital temperature and humidity sensor, 
 * and supports I2C interface (SHT30:0x44 , QMP6988:0x70).
 * QMP6988 is an absolute air pressure sensor specially designed for mobile applications, 
 * with high accuracy and stability, suitable for environmental data collection and detection types of projects.
 * This Unit communicates with the ATOM Lite via the GROVE(I2C+I/0+UART). 
 * 
 * Wiki:
 * https://docs.m5stack.com/en/unit/envIII
 *
 * Product Features:
 * Simple and easy to use
 * High accuracy
 * I2C communication interface
 * HY2.0-4P interface, support platform UIFlow , Arduino
 * 2x LEGO compatible holes
 * 
 */
   // Enable/disable sensor measurements if you want to
   #define ENABLE_SENSOR_ENVIII
   // Sensors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_SENSOR_ENVIII
     // https://github.com/m5stack/M5Unit-ENV
     #include "M5_ENV.h"
     SHT3X sht30;
     QMP6988 qmp6988;
     float env3_tmp      = 0.0;
     float env3_hum      = 0.0;
     float env3_pres     = 0.0;
   #endif 

/*
 * M5STACK UNIT PIR - motion sensor
 *
 * Description:
 * PIR is a human body infrared unit. It belongs to the "passive pyroelectric infrared detector". 
 * It detects the infrared radiation emitted and reflected by the human body or object. 
 * When infrared is detected, the output level is high and it takes a while. 
 * Delay (high during the period and allow repeated triggers) until the trigger signal disappears (restores low).
 * This Unit communicates with the ATOM DTU LoRaWAN868 via the GROVE PORT A.
 * 
 * Wiki:
 * https://docs.m5stack.com/en/unit/pir
 *
 * Product Features:
 * Detects the distance: 500cm
 * latency time: 2s
 * Sensing range: < 100
 * Quiescent current: < 60uA
 * Operating temperature: -20 - 80 C
 * GROVE interface, support UIFlow and Arduino
 * Two Lego installation holes
 *
 */
   // Enable/disable sensor measurements if you want to
   #define ENABLE_SENSOR_PIR_MOTION
   // Sensors enabled, but not found in the hardware will be ignored
   #ifdef ENABLE_SENSOR_PIR_MOTION
     #define PIR_MOTION_SENSOR 21 
     bool MOTION = false;
     int motion_counter = 0;
   #endif 

/*
 * Generally, you should use "unsigned long" for variables that hold time
 * The value will quickly become too large for an int to store
 */
   // this timer is used to update data send frequence
   unsigned long previousMillis = 0;
   // every 10 minutes
   unsigned long interval = 600000;

   // every 5 minutes
   // unsigned long interval = 300000; 

   // every 3 minutes
   // unsigned long interval = 180000;

   // every 1 minute
   // unsigned long interval = 60000;   

   // every 30 seconds
   // unsigned long interval = 30000;   

/*-------------------------------------------------------------------------------*/
/* Function String waitRevice()                                                  */
/*                                                                               */
/* TASK    : wait until we got a response                                        */
/* UPDATE  : 02.01.2023                                                          */
/*-------------------------------------------------------------------------------*/
String waitRevice() 
{
  String recvStr;
  do 
  {
    recvStr = Serial2.readStringUntil('\n');
  } 
  while (recvStr.length() == 0);
    
  DebugPrint("[?] "); 
  DebugPrintln(recvStr);

  return recvStr;
}

/*-------------------------------------------------------------------------------*/
/* Function void array_to_string(byte array[], unsigned int len, char buffer[])  */
/*                                                                               */
/* TASK    : build string out of payload data                                    */
/* UPDATE  : 24.01.2021                                                          */
/*-------------------------------------------------------------------------------*/
void array_to_string(byte array[], unsigned int len, char buffer[])
{
  for (unsigned int i = 0; i < len; i++)
  {
    byte nib1 = (array[i] >> 4) & 0x0F;
    byte nib2 = (array[i] >> 0) & 0x0F;
    buffer[i*2+0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
    buffer[i*2+1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
  }
  buffer[len*2] = '\0';
}

/*-------------------------------------------------------------------------------*/
/* Function void DebugPrint(String)                                              */
/*                                                                               */
/* TASK    : print debug infos on serial monitor                                 */
/* UPDATE  : 05.01.2023                                                          */
/*-------------------------------------------------------------------------------*/
void DebugPrint(String msg)
{
  #ifdef DEBUG_MODE
    Serial.print(msg);
  #endif
}

/*-------------------------------------------------------------------------------*/
/* Function void DebugPrintln(String)                                            */
/*                                                                               */
/* TASK    : print debug infos on serial monitor                                 */
/* UPDATE  : 05.01.2023                                                          */
/*-------------------------------------------------------------------------------*/
void DebugPrintln(String msg)
{
  #ifdef DEBUG_MODE
    Serial.println(msg);
  #endif
} 

/*-------------------------------------------------------------------------------*/
/* Function void send_data(void)                                                 */
/*                                                                               */
/* TASK    : send data to via LoRaWAN to TTN V3                                  */
/* UPDATE  : 02.01.2023                                                          */
/*-------------------------------------------------------------------------------*/
void send_data(void)
{
  // LED color RED
  M5.dis.fillpix(0xff0000);
  // we show always who am i
  DebugPrintln(F("\n\r"));
  DebugPrint(F(application));
  DebugPrint(F(" Version "));
  DebugPrintln(F(aktu_version));
  DebugPrintln(F(""));

  #ifdef ENABLE_SENSOR_ENVIII       
    // obtain the data of qmp6988
    env3_pres = qmp6988.calcPressure();
    DebugPrint("[x] ENVIII Pressure:    ");
    DebugPrintln(String(env3_pres/100)); 

    // obtain the data of shT30
    if (sht30.get() == 0) 
    {     
      // store the temperature obtained from shT30.
      env3_tmp = sht30.cTemp;
      // store the humidity obtained from the SHT30.
      env3_hum = sht30.humidity;
      DebugPrint("[x] ENVIII Temperature: ");
      DebugPrintln(String(env3_tmp));  
      DebugPrint("[x] ENVIII Humidity:    ");
      DebugPrintln(String(env3_hum)); 
      DebugPrintln(F("")); 
    } 
    else 
    {
      env3_tmp = 0, env3_hum = 0;
    }
  #endif

  #ifdef ENABLE_SENSOR_PIR_MOTION
    DebugPrint("[x] PIR Motion:         ");
    DebugPrintln(String(motion_counter)); 
  #endif   

  // new payload - smaller - and better data */
  int  tmp    = ((int)(env3_tmp * 100))+5000;
  int  pre    = (int)(env3_pres / 100 * 10);
  byte hum    = (int)(env3_hum * 2);
  byte motion = (int)(motion_counter);

  byte payload[6];
  payload[0] = tmp >> 8;
  payload[1] = tmp;
  payload[2] = pre >> 8;
  payload[3] = pre;
  payload[4] = hum;
  payload[5] = motion;
     
  char str[32] = "";
  array_to_string(payload, 6, str);   

  DebugPrint(F("[x] actual TTN payload --> "));
  DebugPrintln(str);
  DebugPrintln(F("[x] sending data to TTN V3 ..."));
 
  String AT_command = "AT+DTRX=1,20,6,";
  AT_command = AT_command + str;
  AT_command = AT_command + ("\r\n");
  State = kSending;
  LoRaWAN.writeCMD(AT_command);

  // LED color BLUE
  M5.dis.fillpix(0x0000FF);

  // check if all runbs fine
  String recvStr = waitRevice();
  delay(5000);
  recvStr = waitRevice();
  delay(5000);
  recvStr = waitRevice();
  delay(5000);

  // LED color GREEN
  M5.dis.fillpix(0x00FF00);
  State = kConnected;

  // motion counter reset
  motion_counter = 0;
}
    
/*-------------------------------------------------------------------------------*/
/* Function void setup()                                                         */
/*                                                                               */
/* TASK    : setup all needed requirements                                       */
/* UPDATE  : 01.01.2023                                                          */
/*-------------------------------------------------------------------------------*/
void setup() 
{  
  M5.begin(true, false, true);
  // LED color WHITE
  M5.dis.fillpix(0xFFFFFF);

  // do we debug ? 
  #ifdef DEBUG_MODE
    Serial.begin(115200);
    delay(5000);
  #endif

  // boot application
  DebugPrintln(F(" "));
  DebugPrintln(F(" "));
  DebugPrintln(F("Starting..."));
  DebugPrint(F(application)); Serial.print(F(" Version ")); Serial.println(F(aktu_version));
  DebugPrintln(F("connected via LoRaWAN to TTN V3"));
  DebugPrintln(F(" "));  
  DebugPrintln("[x] Initializing M5Stack Atom_DTU_LoRaWAN868");  
 
  // wire init, adding the I2C bus.
  Wire.begin(26,32);

  #ifdef ENABLE_SENSOR_ENVIII
    // init Unit ENVIII
    DebugPrintln(F("[x] Initializing ENVIII Unit (SHT30 and QMP6988)"));
    qmp6988.init();
  #endif  

  #ifdef ENABLE_SENSOR_PIR_MOTION
    // init Unit PIR
    pinMode(PIR_MOTION_SENSOR, INPUT);
    DebugPrintln(F("[x] Initializing PIR Unit")); 
  #endif   

  // init LoRaWAN Unit
  LoRaWAN.Init(&Serial2, 19, 22);
  delay(100);
 
  while (!LoRaWAN.checkDeviceConnect())

  LoRaWAN.writeCMD("AT?\r\n");
  delay(100);
  Serial2.flush();
    
  // disable Log Information
  DebugPrintln(F(" "));
  DebugPrintln("[x] LoRaWAN Module - disable log informations ..."); 
  LoRaWAN.writeCMD("AT+ILOGLVL=0\r\n");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // enable Log Information
  DebugPrintln("[x] LoRaWAN Module - enable log informations ...");
  LoRaWAN.writeCMD("AT+CSAVE\r\n");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // reboot
  DebugPrintln("[x] LoRaWAN Module - rebooting ...");
  LoRaWAN.writeCMD("AT+IREBOOT=0\r\n");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // Set Join Mode OTAA.
  DebugPrintln("[x] LoRaWAN Module - set mode OTAA with DEV EUI, APP EUI and APP KEY ...");
  LoRaWAN.configOTTA(DevEui,   // DEV EUI
                     AppEui,   // APP EUI
                     AppKey,   // APP KEY
                     "2"       // Upload Download Mode
  );      
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // Set ClassC mode
  DebugPrintln("[x] LoRaWAN Module - set ClassC mode ...");
  LoRaWAN.setClass("2");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // set Work Mode
  DebugPrintln("[x] LoRaWAN Module - set Work mode ...");
  LoRaWAN.writeCMD("AT+CWORKMODE=2\r\n");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // LoRaWAN868
  // TX Freq
  // 868.1 - SF7BW125 to SF12BW125
  // 868.3 - SF7BW125 to SF12BW125 and SF7BW250
  // 868.5 - SF7BW125 to SF12BW125
  // 867.1 - SF7BW125 to SF12BW125
  // 867.3 - SF7BW125 to SF12BW125
  // 867.5 - SF7BW125 to SF12BW125
  // 867.7 - SF7BW125 to SF12BW125
  // 867.9 - SF7BW125 to SF12BW125
  // 868.8 - FSK
  DebugPrintln("[x] LoRaWAN Module - set FreqMask ...");
  LoRaWAN.setFreqMask("0001");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // 869.525 - SF9BW125 (RX2)              | 869525000
  // set RXWindow
  DebugPrintln("[x] LoRaWAN Module - set RXWindow ...");
  LoRaWAN.setRxWindow("869525000");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  // change default port 10 to 2 (1-233 possible)
  DebugPrintln("[x] LoRaWAN Module - change port ...");
  LoRaWAN.writeCMD("AT+CAPPPORT=2\r\n");
  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);

  DebugPrintln("[x] LoRaWAN TTN V3 try to JOIN ...");
  LoRaWAN.startJoin();

  response = LoRaWAN.waitMsg(1000);
  DebugPrint("[>] ");
  DebugPrint(response);
  delay(5000);

  // have we joined ?
  String recvStr = waitRevice();
  if (recvStr.indexOf("+CJOIN:") != -1) 
  {
    if (recvStr.indexOf("OK") != -1) 
    {
      // all runs fine
      DebugPrintln("[x] LoRaWAN TTN V3 JOIN-OK");
      State = kConnected;
      // 3x LED blinking BLUE
      M5.dis.fillpix(0x000000);
      delay(500);
      M5.dis.fillpix(0x0000FF);
      delay(500);
      M5.dis.fillpix(0x000000);
      delay(500);
      M5.dis.fillpix(0x0000FF);
      delay(500);
      M5.dis.fillpix(0x000000);
      delay(500);
      M5.dis.fillpix(0x0000FF);     
    } 
    else 
    {
      // something went wrong
      DebugPrintln("[!] LoRaWAN TTN V3 JOIN-FAIL");
      State = kError;
      // 3x LED blinking RED
      M5.dis.fillpix(0x000000);
      delay(500);
      M5.dis.fillpix(0xFF0000);
      delay(500);
      M5.dis.fillpix(0x000000);
      delay(500);
      M5.dis.fillpix(0xFF0000);
      delay(500);
      M5.dis.fillpix(0x000000);
      delay(500);
      M5.dis.fillpix(0xFF0000);
    }
  }
  delay(5000);
  if (State == kConnected) 
  {
    DebugPrintln("[x] sending first sensor data to TTN V3 ...");
    send_data();
  }   
}

/*-------------------------------------------------------------------------------*/
/* Function void loop()                                                          */
/*                                                                               */
/* TASK    : this runs forever                                                   */
/* UPDATE  : 01.01.2023                                                          */
/*-------------------------------------------------------------------------------*/
void loop() 
{
  if (M5.Btn.wasPressed()) 
  {
    if (State == kConnected) 
    {
      send_data();
    }  
  }

  /*
   * If we have an enabled PIR Motion Sensor we will send immediately
   * a message to the LoRaWan Gateway if we have detected an intruder
   */ 
  #ifdef ENABLE_SENSOR_PIR_MOTION
    int sensorValue = digitalRead(PIR_MOTION_SENSOR);
    // if the sensor value is HIGH we have an intruder ?
    if(sensorValue == HIGH)       
    { 
      if (MOTION == false)
      {
        MOTION=true;
        DebugPrintln("[x] PIR MOTION detected ...");
        // 3x LED blinking RED
        M5.dis.fillpix(0x000000);
        delay(300);
        M5.dis.fillpix(0xFF0000);
        delay(300);
        M5.dis.fillpix(0x000000);
        delay(300);
        M5.dis.fillpix(0xFF0000);
        delay(300);
        M5.dis.fillpix(0x000000);
        delay(300);
        M5.dis.fillpix(0xFF0000);
        motion_counter++;                     
      }
    }       
    // if the sensor value is HIGH we have an intruder ?
    if(sensorValue == LOW)       
    { 
      MOTION=false;
      M5.dis.fillpix(0x00FF00);    
    }
    
  #endif

  /* 
   * It is checked whether the time for the transmission interval has already expired
   * If the time difference between the last save and the current time is greater
   * as the interval, the following function is executed.
  */
  if (millis() - previousMillis > interval)
  {
    // correct timer
    previousMillis = millis();
    // send data to TTN V3
    if (State == kConnected) 
    {
      send_data();
    } 
  }

  delay(50);
  M5.update();

}

Credits

KeHoSoftware

KeHoSoftware

6 projects • 6 followers

Comments