matthijskooijman / arduino-lmic

:warning: This library is deprecated, see the README for alternatives.
706 stars 649 forks source link

MQ-135 Gas Sensor Causing Transmission Issues (More of an FYI) #203

Closed ElectronicallyE closed 5 years ago

ElectronicallyE commented 5 years ago

Hey matthijskooijam,

This is not so much an issue as it is just something I wanted to point out. You can close this, but wanted to leave it here as something to reference back to if necessary.

I am using your library to send sensor data from a TTGO ESP32 SX1276.

I have recently been testing this with a MQ-135 gas sensor as I have had success with the library previously. My issue is in regards to errors when plugging the data line from the sensor into the TTGO.

When the sensor is not plugged in, my code works perfectly and gives me the expected: 3353: EV_Joining When the sensor's GND and VCC lines are connected to the TTGO, I get the expected: 3353: EV_Joining When all the sensor is connected with GND VCC and the data line AD, I get:

FAILURE 
/Users/lachlanetherton/Documents/Arduino/libraries/arduino-lmic-master/src/lmic/radio.c:689

For interest sakes, when the sensor's GND and VCC lines are connected and then I plug in the data line after seeing the line EV_Joining, I get the error:

FAILURE 
/Users/lachlanetherton/Documents/Arduino/libraries/arduino-lmic-master/src/lmic/radio.c:545

I can assure you that this issue does not occur when using any other sensor and my board and data pin (14) is the same between tests.

When changing the pin to 12 instead of 14, there is no issue. This is likely due to pin 14 being connected to the LoRa_RST line.

What fascinates me about this is a few things:

  1. Why does a DHT11 temperature and humidity sensor connected to pin 14 not have the same problem? Is it because it's doing an alternative reading function from the data line, defined in the DHT sensor library vs the analogRead function, which read the ADC value in the code below?

  2. If I comment out both the definition of the pin and its pinMode and the variable g is set to equal to 24, it still comes back with the same errors above. Is this because of an interference caused by the pin being connected with the LoRa_RST line?

  3. For some reason, the DHT11 must use pin 14, otherwise it won't work. Seems that there is something special about that pin.

This is my code:

#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

#define BUILTIN_LED 25

//Here we use pin IO14 of ESP32 to read data
#define GASPIN 12

static int g;
static uint8_t garray[sizeof(g)];
static int g2;

// APPEUI needs to be in little endian / least-significant-byte (lsb) format
// This is found by clicking on < > next to "Device EUI" and then the two arrows icon 
static const u1_t PROGMEM APPEUI[8] = {  };
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

// DEVEUI needs to be in little endian / least-significant-byte (lsb) format
// This is found by clicking on < > next to "Device EUI" and then the two arrows icon 
static const u1_t PROGMEM DEVEUI[8] = {  };
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

// APPKEY needs to be in big endian / most significant byte format (msb) format
// This is found by clicking on < > next to "Device EUI"
static const u1_t PROGMEM APPKEY[16] = {  };
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 30;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = 18,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 14,
  .dio = {26, 33, 32},
//  .dio = {26, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN},
};

void onEvent (ev_t ev) {
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev) {
    case EV_SCAN_TIMEOUT:
      Serial.println(F("EV_SCAN_TIMEOUT"));
      break;
    case EV_BEACON_FOUND:
      Serial.println(F("EV_BEACON_FOUND"));
      break;
    case EV_BEACON_MISSED:
      Serial.println(F("EV_BEACON_MISSED"));
      break;
    case EV_BEACON_TRACKED:
      Serial.println(F("EV_BEACON_TRACKED"));
      break;
    case EV_JOINING:
      Serial.println(F("EV_JOINING"));
      break;
    case EV_JOINED:
      Serial.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:
      Serial.println(F("EV_RFU1"));
      break;
    case EV_JOIN_FAILED:
      Serial.println(F("EV_JOIN_FAILED"));
      break;
    case EV_REJOIN_FAILED:
      Serial.println(F("EV_REJOIN_FAILED"));
      //break;
      break;
    case EV_TXCOMPLETE:
      Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      digitalWrite(BUILTIN_LED, LOW);
      if (LMIC.txrxFlags & TXRX_ACK) {
        Serial.println(F("Received ack"));
      }
      if (LMIC.dataLen) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      // Schedule next transmission
      os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
      break;
    case EV_LOST_TSYNC:
      Serial.println(F("EV_LOST_TSYNC"));
      break;
    case EV_RESET:
      Serial.println(F("EV_RESET"));
      break;
    case EV_RXCOMPLETE:
      // Data received in ping slot
      Serial.println(F("EV_RXCOMPLETE"));
      break;
    case EV_LINK_DEAD:
      Serial.println(F("EV_LINK_DEAD"));
      break;
    case EV_LINK_ALIVE:
      Serial.println(F("EV_LINK_ALIVE"));
      break;
    default:
      Serial.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) {
    Serial.println(F("OP_TXRXPEND, not sending"));
  } else {
    // Prepare upstream data transmission at the next possible time.
    LMIC_setTxData2(1, garray, sizeof(garray) - 1, 0);

    Serial.println(F("Packet queued"));
    digitalWrite(BUILTIN_LED, HIGH);
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);

  SPI.begin(5, 19, 27);

  pinMode(GASPIN,INPUT); // Set sensor for input

  //Serial.println("The MQ-135 sensor just started up! #Finallyitsworking!");
  // Call begin to start sensor

  // Read temperature as Celsius (the default)
  g = analogRead(GASPIN);
  //g = 24;
  memcpy(garray, &g, sizeof(garray)); //Choose the destination in memory where garray is, find the source of g to be copied and copy the size of garray and paste it in the location of garray. For more information, view http://www.cplusplus.com/reference/cstring/memcpy/ 

  // Check if any reads failed and exit early (to try again).
  if (g>=4095) {
    Serial.println("Failed to read gas from MQ-135 sensor!");
    return;
  }

  memcpy(&g2, &garray, sizeof(g2)); // Choose the destination in memory where g2 is, find the source of garray to be copied and copy the size of g2 and paste it in the location of g2. For more information, view http://www.cplusplus.com/reference/cstring/memcpy/ 
  Serial.print("To show you the sensor is working, this is the value it's outputting: ");
  Serial.println(g2); 

  // LMIC init
  os_init();

  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, LOW);
}

void loop() {
  os_runloop_once();
}

And here is the DHT11 code:

#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

#include "DHT.h"

#define BUILTIN_LED 25

//Here we use pin IO14 of ESP32 to read data
#define DHTPIN 14

//Our sensor is DHT11 type. Swap to DHT22 if using such a sensor.
#define DHTTYPE DHT11

//Create an instance of DHT sensor
DHT dht(DHTPIN, DHTTYPE);

// Temperature
// Choose either temperature or humidity
static int t;
static uint8_t tarray[sizeof(t)];
static int t2;

// Humidity
// Choose either temperature or humidity
//static int h;
//static uint8_t harray[sizeof(t)];
//static int h2;

// APPEUI needs to be in little endian / least-significant-byte (lsb) format
// This is found by clicking on < > next to "Device EUI" and then the two arrows icon 
static const u1_t PROGMEM APPEUI[8] = {  };
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

// DEVEUI needs to be in little endian / least-significant-byte (lsb) format
// This is found by clicking on < > next to "Device EUI" and then the two arrows icon 
static const u1_t PROGMEM DEVEUI[8] = {  };
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

// APPKEY needs to be in big endian / most significant byte format (msb) format
// This is found by clicking on < > next to "Device EUI"
static const u1_t PROGMEM APPKEY[16] = {  };
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 30;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = 18,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 14,
  .dio = {26, 33, 32},
//  .dio = {26, LMIC_UNUSED_PIN, LMIC_UNUSED_PIN},
};

void onEvent (ev_t ev) {
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev) {
    case EV_SCAN_TIMEOUT:
      Serial.println(F("EV_SCAN_TIMEOUT"));
      break;
    case EV_BEACON_FOUND:
      Serial.println(F("EV_BEACON_FOUND"));
      break;
    case EV_BEACON_MISSED:
      Serial.println(F("EV_BEACON_MISSED"));
      break;
    case EV_BEACON_TRACKED:
      Serial.println(F("EV_BEACON_TRACKED"));
      break;
    case EV_JOINING:
      Serial.println(F("EV_JOINING"));
      break;
    case EV_JOINED:
      Serial.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:
      Serial.println(F("EV_RFU1"));
      break;
    case EV_JOIN_FAILED:
      Serial.println(F("EV_JOIN_FAILED"));
      break;
    case EV_REJOIN_FAILED:
      Serial.println(F("EV_REJOIN_FAILED"));
      //break;
      break;
    case EV_TXCOMPLETE:
      Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      digitalWrite(BUILTIN_LED, LOW);
      if (LMIC.txrxFlags & TXRX_ACK) {
        Serial.println(F("Received ack"));
      }
      if (LMIC.dataLen) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      // Schedule next transmission
      os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
      break;
    case EV_LOST_TSYNC:
      Serial.println(F("EV_LOST_TSYNC"));
      break;
    case EV_RESET:
      Serial.println(F("EV_RESET"));
      break;
    case EV_RXCOMPLETE:
      // Data received in ping slot
      Serial.println(F("EV_RXCOMPLETE"));
      break;
    case EV_LINK_DEAD:
      Serial.println(F("EV_LINK_DEAD"));
      break;
    case EV_LINK_ALIVE:
      Serial.println(F("EV_LINK_ALIVE"));
      break;
    default:
      Serial.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) {
    Serial.println(F("OP_TXRXPEND, not sending"));
  } else {
    // Prepare upstream data transmission at the next possible time.

    // Temperature
    LMIC_setTxData2(1, tarray, sizeof(tarray) - 1, 0);

    // Humidity
    //LMIC_setTxData2(1, harray, sizeof(harray) - 1, 0);

    Serial.println(F("Packet queued"));
    digitalWrite(BUILTIN_LED, HIGH);
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);

  SPI.begin(5, 19, 27);

  Serial.println("The DHT11 sensor just started up! #Finallyitsworking!");
  // Call begin to start sensor
  dht.begin();

  // Read temperature as Celsius (the default)
  t = dht.readTemperature();
  memcpy(&tarray, &t, sizeof(tarray)); //Choose the destination in memory where tarray is, find the source of t to be copied and copy the size of tarray and paste it in the location of tarray. For more information, view http://www.cplusplus.com/reference/cstring/memcpy/ 

  // Check if any reads failed and exit early (to try again).
  if (t>=50) {
    Serial.println("Failed to read temperature from DHT sensor!");
    return;
  }

  memcpy(&t2, &tarray, sizeof(t2)); // Choose the destination in memory where t2 is, find the source of tarray to be copied and copy the size of t2 and paste it in the location of t2. For more information, view http://www.cplusplus.com/reference/cstring/memcpy/ 
  Serial.print("To show you the sensor is working, this is the temperature: ");
  Serial.print(t2);
  Serial.println(" *C");  

//  // Read humidity as a percentage. The higher it is, the closer you are to Darwin.
//  h = dht.readHumidity();
//  memcpy(&harray, &h, sizeof(harray)); //Choose the destination in memory where harray is, find the source of h to be copied and copy the size of harray and paste it in the location of harray. For more information, view http://www.cplusplus.com/reference/cstring/memcpy/ 
//    
//  // Check if any reads failed and exit early (to try again).
//  if (h>=100) {
//    Serial.println("Failed to read humidity from DHT sensor!");
//    return;
//  }
//
//  memcpy(&h2, &harray, sizeof(h2)); // Choose the destination in memory where h2 is, find the source of harray to be copied and copy the size of h2 and paste it in the location of h2. For more information, view http://www.cplusplus.com/reference/cstring/memcpy/ 
//  Serial.print("To show you that the sensor is working, this is the humidity: ");
//  Serial.print(h2);
//  Serial.println(" %");  

  // LMIC init
  os_init();

  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();

  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, LOW);
}

void loop() {
  os_runloop_once();
}
matthijskooijman commented 5 years ago

Interesting problem. But in general, using one pin for multiple things is not a good idea. So I'm pretty sure the proper solution is to use a different pin for the sensors.

But then the question is: Why does it behave like it does? I'm not really familiar with the ESP32 platform, but I'll try to provide some ideas anyway.

From your code, I see that you read the sensor, and afterwards initialize LMIC. This should mean it doesn't really matter what you do when reading the sensor, only the state you leave things in matters. Since IIRC LMIC will just configure the reset pin as an output pin, the pinmode of pin 14 does not matter either.

However, it seems your gas sensor simply outputs an analog voltage on its output, meaning it is continuously driving the output. This has two problems:

  1. When the LMIC makes pin 14 an output, driving it either LOW or HIGH, there will be two drivers for the same line, competing to set a give voltage level. This might cause a short-circuit or otherwise excessive current, and damage pins.
  2. When the gas sensor "wins" this competition and manages to drive the voltage on pin 14 below a certain threshold, the SX chip will see that as a LOW level and reset itself. I suspect this is why you see these asserts.

Then why does this work for the DHT chip? IIRC, the DHT chip has a request-response protocol, where the Arduino must request data, and only then will the DHT chip send data to the pin. When neither is sending data, the pin level is determined by the pullup. Then, if LMIC makes pin 14 an output, and drives it LOW or HIGH, that will have not trouble competing with the pullup, so LMIC determine the voltage level as normal.

Note that if you would read the DHT sensor after initializing LMIC, this would drive pin 14 LOW during communication with the DHT chip, and the SX chip would reset again (possibly resulting in another assertion failure).

Since this is not an issue in LMIC, I'm closing this issue. Feel free to post followup comments, though :-)

ElectronicallyE commented 5 years ago

Hey Matt,

Thanks for the comment. That makes sense. Appreciate the explanation and keep up the good work with the library.