OPEnSLab-OSU / Loom

Arduino library for Internet of Things Rapid Prototyping in environmental sensing
GNU General Public License v3.0
26 stars 3 forks source link

Hypnos failing to exit Deep Sleep with Rainsavor #163

Closed rcpeene closed 2 years ago

rcpeene commented 2 years ago

Describe the bug Hypnos Deep Sleep Functionality not waking up on Rainsavor. After going to sleep, Rainsavor would simply not wake up from sleep and would not log to the SD card.

Hardware in Use Feather M0 Proto, Hypnos board, custom Rainsavor PCB, Rainsavor siphon with diodes

To Reproduce Steps to reproduce the behavior:

  1. Set up a functional Rainsavor stack
  2. Connect it to the Rainsavor siphon
  3. Compile and upload the Rainsavor_Interrupt.ino sketch
  4. Fill the siphon with water and watch it drain
  5. Watch the LED and Serial monitor for signs of Interrupt behavior
  6. Disconnect the stack and read the SD card to see if data was logged

Expected behavior When the siphon is filled with water, the LED should blink. Afterward, a conductivity data entry should be present on the SD card for each time an interrupt was triggered.

Additional context Testing the Loom Interrupt examples (no deep sleep) the Interrupts appeared to work correctly. Testing the full system code with deep sleep not work.

rcpeene commented 2 years ago
/*
 * OPEnS Lab
 * Software for the RainSavor Project
 * Dr Chet Udell, Dr Segura
 * 
 * This software operates the electronics for the RainSavor project under the OPEnS lab at Oregon State University and is open source. All of the supporting files, such as electrical design, mechanical design, etc., 
 * are hosted on the OPEnS Lab wiki. 
 * The goal of this system is to sample rainfall in storms at installed locations. This software is the basis for data sampling. All of the sensing is done in hardware with a custom sensor, this software is 
 * peripheral to that sensor. This sketch will simply sample the voltages at the ADC and log it to an SD along with a timestamp. This sketch also utilizes a deep sleep mode to increase battery length
 * and field duration of the system. An interrupt system is used with the deep sleep to allow the controller to take a sample during rainfall events. 
 * 
 * This code heavily utilizes the OPEnS Loom library and software. It also assumes a OPEnS Hypnos board is being used for power control, RTC access, and SD logging. These must be present for correct operation. 
 * 
 */

//Initlialize libraries
#include <Loom.h>
#include <Wire.h>

//Declare Constants & Variables
#define HYPNOS3 5
const byte VR_ADDR = 0x2E;  //I2C address for the AD5246 (variable resistor)
const byte VR_VAL = 40;

#define pin_select 12

int measure = 0;

//Initialize Loom dependencies
const char* json_config =
#include "config.h"
;

LoomFactory<
  Enable::Internet::Disabled,
  Enable::Sensors::Enabled,
  Enable::Radios::Disabled,
  Enable::Actuators::Disabled,
  Enable::Max::Disabled
>ModuleFactory{};

LoomManager Loom{ &ModuleFactory };

//Create the function that occurs when the interrupt triggers
void interruptFunction() {
  if (measure == 0)
  {
    LPrintln("\nButton Pressed Test");
    //Loom.measure();
    //Loom.package();
    measure = 1;
  }
}

//Custom function to communicate with I2C devices on the bus
void send_data(byte addr, byte data) {
  Wire.beginTransmission(addr);

  Wire.write(data);

  Wire.endTransmission(true);
}

//General Setup
void setup() {
  //Enable correct power rails from Hypnos
  pinMode(HYPNOS3, OUTPUT);
  digitalWrite(HYPNOS3, LOW);
  Wire.begin();

  //Begin serial communication as usual
  Loom.begin_serial(true);

  //Setup Loom
  Loom.parse_config(json_config);

  //Interrupt stuff
  pinMode(pin_select, INPUT);
  Loom.InterruptManager().register_ISR(pin_select,interruptFunction,LOW,ISR_Type::IMMEDIATE);
  Loom.InterruptManager().print_config();
  //Send data to AD5246 Variable Resistor
  //send_data(VR_ADDR, VR_VAL);

  LPrintln("\n** SETUP COMPLETE **");

}

void loop() {

  if (measure == 1)
  {
    digitalWrite(HYPNOS3, LOW);
    delay(50);
    send_data(VR_ADDR, VR_VAL);
    //measure = 0;
    delay(50);//was 400
    Loom.record();
    Loom.display_data();
    //Loom.pause();
    Loom.SDCARD().log();
    delay(1000);
    measure = 0;
  }

  digitalWrite(HYPNOS3, HIGH);

}
rcpeene commented 2 years ago
"{\
  'general':\
  {\
    'name':'Device',\
    'instance':1,\
    'interval':5000\
  },\
  'components':[\
    {\
      'name':'ADS1115',\
      'params':'default'\
      },\
      {\
      'name':'SD',\
      'params':[true,1000,10,'data',true]\
    },\
    {\
      'name':'DS3231',\
      'params':[11,true]\
    },\
    {\
      'name':'SHT31D',\
      'params':'default'\
    },\
    {\
      'name':'Interrupt_Manager',\
      'params':[0]\
    },\
    {\
      'name':'Sleep_Manager',\
      'params':[true,false,1]\
    }\
  ]\
}"
rcpeene commented 2 years ago

There appeared to be many challenges which were preventing this from working correctly.

Firstly, with the particular setup of Rainsavor, we required a change to be made to Loom's source code. It can be noted that in the Rainsavor sketch, in setup(), just before Loom.InterruptManager.register_ISR() is called, we set the pin mode to INPUT. In the source code of Loom V2.5, Interrupt_Manager.cpp, Line 211, the pin mode is changed back to INPUT_PULLUP. This was not sufficient for the particular electrical set up of Rainsavor, so INPUT_PULLUP had to be changed to be INPUT in the source code.

rcpeene commented 2 years ago

Additionally, the insight for the following solution was found in Loom's Examples > Lab Examples > Hypnos_SD_Sleep.ino, with this code snippet that appears at the beginning of loop()

    digitalWrite(5, LOW); // Disable 3.3V rail
    digitalWrite(6, HIGH);  // Disable 5V rail
    digitalWrite(13, HIGH);

    // As it turns out, if the SD card is initialized and you change
    // the states of the pins to ANY VALUE, the SD card will fail to
    // write. As a result, we ensure that the board has been turned
    // off at least once before we make any changes to the pin states
    if (rtc_flag) {
        pinMode(23, OUTPUT);
    pinMode(24, OUTPUT);
    pinMode(10, OUTPUT);

        // delay(1000);

    Loom.power_up();
    }

For reason's that we don't fully understand, Loom.power_up() must be called prior to continuing the loop and logging to the SD card, after waking up from Sleep. This seemed to allow Loom to log to SD card. And wake up from sleep at least.

rcpeene commented 2 years ago

There was yet one problem left after adding Loom.power_up() to the loop after sleep. Rainsavor would wake from sleep and correctly log to the SD card, but it would do so when the water level in the siphon emptied past the diodes instead of when the water first filled to electrically connect the two diodes in the siphon. This appeared to be unusual "inverse" behavior, interrupting slightly later than we anticipated and not upon the water's first contacting the diodes.

Originally we thought it had to with the third parameter in the call to Loom.InterruptManager.register_ISR(), where we provided LOW. We wanted the interrupt to occur when the signal was LOW, but as it happened, we were getting interrupts on HIGH. Changing this value to HIGH did indeed change the behavior of the interrupt. It now interrupted when the siphon was filling when the signal should have been LOW. It basically operated "correctly" but was still doing inverse of what we had coded it to do.

The solution was simple. We discovered that the logic was not happening inversely as it appeared. We were not detaching the interrupt, so the interrupt was repeatedly being called until the water level dropped, when the interrupt function was finally allowed to continue. Therefore the fix was to add a detachInterrupt() in the interrupt function, AND importantly a call to reconnectInterrupt() in the loop body which is run after an interrupt is called.