LowPowerLab Forum

Hardware support => Low Power Techniques => Topic started by: LCSteve on June 15, 2020, 10:01:48 AM

Title: Suggestions to save more power with Moteino R6 on shield
Post by: LCSteve on June 15, 2020, 10:01:48 AM
So after a lot of learning and experimenting I have my code working based off the one originally used by user blebson for controlling blinds. I've got a featherwing 8266 with a RFM69 shield as my gateway and is communicating fine with my 2 Moteino's. They sit atop basically relay shields with lipo & solar charging capabilities. I've had them deployed in a testing capacity for nearly 5 days now and I can see code still needs some work to save power as they are slowly eating away at the 2800mah lipo's on them about 10% a day. I tried measuring the consumption power best I could with my DVM and it looks like it was swinging back and forth between .670mA & 1.300mA. I assume this is happening because the call for radio.listenModeStart() with a LowPower.powerDown (SLEEP_60MS, ADC_OFF, BOD_OFF) which if I understand is putting the Moteino asleep and waking back up to listen for incoming requests. I tried using the "SLEEP_FOREVER" option but when I do it never responds to requests from the gateway. This is also as I would expect from some older posts since that it would turn off the radio and wait for a timer or external input to wake back up. Since I ultimately need the node to receive requests from the gateway I need a method which keeps the radio awake to some degree.

I'm looking for some suggestions from more seasoned users and have included an image of the shield with the Moteino on it (from blebsons posting hence the R4 on the chip), the pin mapping as seen and the node code I'm currently using. Thanks for the help and suggestions.

Code: [Select]
#include <RFM69.h>  //Library 1.4.0 https://github.com/LowPowerLab/RFM69
#include <SPI.h>
#include <LowPower.h>  //Library 1.2.1
#include <Servo.h>  //Library 1.1.6

// Uncomment the appropriate frequency for your hardware
#define FREQUENCY     RF69_915MHZ

//ENCRYPT_KEY must match gateway value and must be exactly 16 characters
//#define ENCRYPT_KEY "mysecretmysecret"
#define NODEID 1  //This is the right blind
//#define NODEID 2 //This is the left blind
#define NETWORKID 100
#define GATEWAYID   1
#define BLINDS_1   0b00000001  //This is the right blind
//#define BLINDS_2   0b00000010 //This is the left blind
#define MY_NODE_FLAG BLINDS_1  //This is the right blind
//#define MY_NODE_FLAG BLINDS_2 //This is the left blind

struct PACKET {
  int16_t percent;
  int16_t voltage1;
  int16_t voltage2;
  char charger;
} packetOut;

struct inPACKET {
  uint8_t
  group,
  value;
} packetIn;

RFM69 radio;
const int wakeUpPin = 3;
const int moveUpPin = 4;
const int servoPowerPin = 5;
const int moveDownPin = 7;
const int servoPin = 6;
const int okStatus = 16;
const int chStatus = 15;
int currentAngle = 0;
Servo blindServo;

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

  radio.initialize(FREQUENCY, NODEID, NETWORKID); 
//  radio.encrypt(ENCRYPT_KEY);
  char buff[50];
  sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
  Serial.println(buff);

  radio.listenModeEnd();
  Serial.print("Network ID: ");
  Serial.print(NETWORKID);
  Serial.print(", Node ID: ");
  Serial.println(NODEID);
  pinMode(wakeUpPin, INPUT);
  pinMode(moveUpPin, INPUT);
  pinMode(moveDownPin, INPUT);
  pinMode(servoPowerPin, OUTPUT);
  pinMode(A0, INPUT);
  pinMode(okStatus, INPUT_PULLUP);
  pinMode(chStatus, INPUT_PULLUP);
  blindServo.attach(servoPin, 500, 2500); //This is the right blind
//  blindServo.attach(servoPin, 400, 2400); //This is the left blind
  blindServo.write(currentAngle); //Sets the starting angle at 0
//  Serial.println(blindServo.read());
}

void loop() {
  int angle; 
//This section is for the manual switch

  attachInterrupt(1, wakeUpSwitch, HIGH);
  if (digitalRead(moveUpPin) == HIGH) {
    while(digitalRead(moveUpPin) == HIGH) {
      digitalWrite(servoPowerPin, HIGH);
      delay(75);
      currentAngle = blindServo.read();
     
      if (currentAngle < 175) {
        currentAngle += 5;
        blindServo.write(currentAngle);
        delay(50);
//        Serial.print("Opening, Setting servo angle to: ");
//        Serial.println(blindServo.read());
      }
      else if (currentAngle < 180 && currentAngle > 175) {
        currentAngle += 1;
        blindServo.write(currentAngle);
        delay(50);
//        Serial.print("Opening, Setting servo angle to: ");
//        Serial.println(blindServo.read());
      }
      else {
          blindServo.write(180);
//          Serial.println("Setting servo angle to: 180");
      }
    }
  }
 
  else if (digitalRead(moveDownPin) == HIGH) {
    while(digitalRead(moveDownPin) == HIGH){
      digitalWrite(servoPowerPin, HIGH);
      delay(75);
      currentAngle = blindServo.read();
   
    if (currentAngle > 5) {
      currentAngle -= 5;
      blindServo.write(currentAngle);
      delay(50);
//      Serial.print("Closing, Setting servo angle to: ");
//      Serial.println(blindServo.read());
    }
    else if (currentAngle > 0 && currentAngle < 5) {
      currentAngle -= 1;
      blindServo.write(currentAngle);
      delay(50);
//      Serial.print("Closing, Setting servo angle to: ");
//      Serial.println(blindServo.read());
    }
    else {
      blindServo.write(0);
//      Serial.println("Closing, Setting servo angle to: 0");
    }
  }
  }

//End of the manual switch section
 
  else  {
    if (radio.receiveDone())  {
//      Serial.print('[');Serial.print(radio.SENDERID, DEC);Serial.print("] ");
//      Serial.print("[RX_RSSI:");Serial.print(radio.readRSSI());Serial.println("]");
//      Serial.print("Payload Size: ");Serial.println(sizeof(inPACKET));//Serial.print(" ");
//      Serial.print("DataLen Size: ");Serial.println(radio.DATALEN);//Serial.print(" ");
     
        if (radio.ACKRequested()) {
          radio.sendACK();
        }
        Serial.println("Received a normal message");
        Serial.println((char*)radio.DATA);
        Serial.flush();
    }

    radio.listenModeStart();
    if (digitalRead(wakeUpPin) == LOW) {
      digitalWrite(servoPowerPin, LOW);
      delay(5);
//      Serial.println("Entering low-power listen mode...");
      Serial.flush();
      LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
//      LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
//       Woke up, check for a message
//      delay(2000);
//      Serial.println("Woke up!");
//      delay(25);
//      detachInterrupt(1);
    }

    int sensorValue = analogRead(A0);
      delay(1);
    sensorValue = analogRead(A0);

//    Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 5V):
    float voltage = ((sensorValue * (3.3 / 1023.0)) * 2);

    uint8_t percent = ((voltage - 3.3) / 0.9125) * 100;
    packetOut.percent = percent;
   
    packetOut.voltage1 = voltage;
//    packetOut.voltage1 = (voltage * 100);
    packetOut.voltage2 = (voltage * 100);

//    Serial.println(sensorValue);   
//    Serial.print("Battery Percent: ");
//    Serial.println(packetOut.percent);
//    Serial.print("Battery Voltage: ");
//    Serial.println(voltage);
//    Serial.print("Battery Voltage1: ");
//    Serial.println(packetOut.voltage1);
//    Serial.print("Battery Voltage2: ");
//    Serial.println(packetOut.voltage2);

//    Check the status of the solar charger
    if (digitalRead(okStatus) == LOW && digitalRead(chStatus) == HIGH) {
//      packetOut.charger = 'O';
      packetOut.charger = 79; //Converted output to ASCII
//      Serial.println(digitalRead(okStatus));
//      Serial.println(digitalRead(chStatus));
//      Serial.println("Solar: Fully Charged");
    }
    else if (digitalRead(okStatus) == HIGH && digitalRead(chStatus) == LOW) {
//      packetOut.charger = 'C';
      packetOut.charger = 67;
//      Serial.println(digitalRead(okStatus));
//      Serial.println(digitalRead(chStatus));
//      Serial.println("Solar: Charging");
    }
    else if (digitalRead(okStatus) == LOW && digitalRead(chStatus) == LOW) {
//      packetOut.charger = 'E';
      packetOut.charger = 69;
//      Serial.println(digitalRead(okStatus));
//      Serial.println(digitalRead(chStatus));
//      Serial.println("Solar: ERROR");
    }
    else {
//      packetOut.charger = 'N';
      packetOut.charger = 78;
//      Serial.println(digitalRead(okStatus));
//      Serial.println(digitalRead(chStatus));
//      Serial.println("Solar: Not Charging");
    }

  int initialPosition = blindServo.read();
  uint8_t from = 0;
  long burstRemaining = 0;
   
    if (radio.DATALEN > 0) {
//      Serial.println("Received a message in listen mode");
//      Serial.println((char*)radio.DATA);

      if (radio.TARGETID == 255)  {
//  Entire section commented out for now

//        struct inPACKET
//        *myPacket = (struct inPACKET *)radio.DATA;
//        angle = (myPacket->value);
//        if (myPacket->group & MY_NODE_FLAG)
//        {         
//          digitalWrite(servoPowerPin, HIGH);
//          delay(75);
//          currentAngle = blindServo.read();
//          //Serial.print("Angle: ");
//          //Serial.println(angle);
//          while (currentAngle != angle) {
//            if (currentAngle > angle) {
//              currentAngle -= 1;
//              blindServo.write(currentAngle);
//              delay(15);
//            }
//           
//            else if (currentAngle < angle) {
//              currentAngle += 1;
//              blindServo.write(currentAngle);
//              delay(15);
//            }
//          }
//        }
      }
     
      else if ( radio.DATA != null && radio.TARGETID == NODEID) {       
        struct inPACKET
        *myPacket = (struct inPACKET *)radio.DATA;
        angle = (myPacket->value);
       
        if ( angle != 0 ) {
          digitalWrite(servoPowerPin, HIGH);
          delay(75);
          currentAngle = blindServo.read();
//          Serial.print("Angle: ");
//          Serial.println(angle);
          while (currentAngle != angle) {
            if (currentAngle > angle) {
              currentAngle -= 1;
              blindServo.write(currentAngle);
              delay(15);
            }
            else if (currentAngle < angle) {
//              Serial.println(blindServo.read());
              currentAngle += 1;
              blindServo.write(currentAngle);
              delay(15);
            }
          }
        }         
      }
     
      delay(125);
      Serial.flush();
      from = radio.SENDERID;
      burstRemaining = radio.RF69_LISTEN_BURST_REMAINING_MS;
    }

    // Radio goes back to standby, ready for normal operations
    radio.listenModeEnd();

    if (from) {
      while (burstRemaining > 0) {
//        LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF);
        burstRemaining -= 60;
      }
//      LowPower.powerDown(SLEEP_30MS, ADC_OFF, BOD_OFF);
      radio.send(from, &packetOut, sizeof(packetOut));
    }
  }
}

void wakeUpSwitch() {
//  Serial.println("Waking up due to switch.");
}

Thanks,
Steve
Title: Re: Suggestions to save more power with Moteino R6 on shield
Post by: Felix on June 15, 2020, 11:11:14 AM
The native ListenMode is declared buggy and the only usable function of ListenMode is ListenMode sleeping (https://github.com/LowPowerLab/RFM69/blob/master/Examples/ListenModeSleep/ListenModeSleep.ino) which helps shave a few uA over WDT sleep.

FWIW:
The 9.1 release of the PiGateway software (https://lowpowerlab.com/2020/04/19/gateway-v9-1-release/) includes the ability to send delayed "REQUESTS" to sleepy nodes.
This would require the end node to periodically contact the gateway and read any ACK payloads for those requests.
So the requests would not be instant and would be limited by the period the node contacts the gateway. But would allow the node to deep sleep. To make that time delta small, you tradeoff some battery life for more frequent transmissions to the Gateway.
Title: Re: Suggestions to save more power with Moteino R6 on shield
Post by: LCSteve on June 15, 2020, 05:12:27 PM
Thanks for the suggestions, I'll need to look into using WDT sleep as an option. So I tried something this afternoon I changed the SLEEP_60MS to 500MS and ultimately 1S. Something I didn't expect to happen, did. Where I was swinging back and forth between .670mA & 1.300mA at 60MS I expected at 500MS to just hold the .670mA longer and thus maybe provide some more savings but instead I'm seeing .007mA hold then quick bump to ~.700mA. What was the floor is now the ceiling so this is great. [Strangely the other Moteino while attached to the same shield only drops to 11uA with the same code, I'm not concerned about it since the values are sure to help my battery situation]. I then upped things to 1S and tried multiple tests and initialing a servo move from the WiFi gateway worked every time. The browser tries long enough to catch it while powered up.

Does the WDT Sleep function accomplish the same goal as the sleep function I'm using just more efficiently? The unit is essentially powered off and woken back up by the watch dog timer?
Title: Re: Suggestions to save more power with Moteino R6 on shield
Post by: Felix on June 16, 2020, 08:50:48 AM
You can sleep the MCU in various ways.
The simplest way is using the LowPower library (https://github.com/LowPowerLab/LowPower).
For WDT sleep you call sleep(TIME_VARIABLE, OPTIONS). This takes around 6uA on a Moteino. The MCU wakes from an interrupt after the timer expires.
With Forever sleep is saves around 2-3 uA more, the MCU never wakes except from external interrupts you enable (ex: a PIR sensor senses motion): powerDown(OPTIONS).
Remember any other current drains in your circuit are on top of the MCU sleep current, including sensors or unintended faults that use power.
Then there is the "ListenMode sleep" illustrated in the ListenModeSleep example (https://github.com/LowPowerLab/RFM69/blob/master/Examples/ListenModeSleep/ListenModeSleep.ino) of the RFM69 library. This enables fine grain sleeping/waking with the benefit of a few uA savings over WDT sleep - at the core it uses "forever sleep" and wakes the MCU after a the given time in ms.