2

I have built an automated window blind controller that will adjust the angle of a servo based on the value received via a light dependent resistor. Once every 10 minutes the MCU wakes up, gets the current value of the LDR, adjusts the angle of the servo accordingly and then goes back to SLEEP_MODE_PWR_DOWN using the watchdog timer. I am using a 5v 16MHz ATMega168 Arduino Pro Mini as the MCU. I have removed both of the on board LEDs to reduce idle power consumption. I am also powering directly from the VCC pin so that I bypass the on board regulator reducing power consumption even more. I'm trying to determine what is the best battery power option for this project. The project is still in testing but appears to be working as intended.

Here is the sketch:

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <VarSpeedServo.h>

const int ldrPin = A0;          //Analog pin that the light dependent resistor is connected to
const int ldrPower = 8;         //Supply 5v to ldr just before reading and then shutoff after
const int servoPin = 9;         //Digital pin that the Clone Futaba S3003 servo is connected to


//PARAMETERS FOR LOW BATTERY WARNING
const int xVPin = A1;             //Analog pin that the external +V non-boosted power supply is connected to
const int lowVLED = 10;           //Digital pin LED is connected to.  Used to identify battery levels
const int flashDelay = 250;       //LED flash length for low battery warning
const int numFlash_Low = 3;       //Number of LED flashes for LOW battery warning
const int numFlash_Mid = 1;       //Number of LED flashes for MID battery warning
int runningTotalVoltage;          //Variable that stores the sum total of the voltage readings
float voltage = 0.00;             //Calculate voltage
unsigned sampleCount = 0;         //Variable that stores the current voltage sampling number
unsigned char LEDFlashCount = 0;  //Variable that stores the current LED flash number
const int voltageSamples = 20;    //Total number of voltage readings to take
float boostVoltage = 5.27;        //Variable for the output voltage from the 5v boost coverter
float minValert = 3.20;           //Variable for the very low battery level (for 3.7v Li-on battery)
float midValert = 3.60;           //Variable for early warning low battery level (for 3.7v Li-on battery)

//PARAMETERS FOR THE SERVO
VarSpeedServo myservo;        //Create servo object to control servo
const int angle1 = 2350;      //Variable for "Fully Closed" servo moving counter-clockwise rotation
const int angle2 = 1900;      //Variable for "Partially Open" servo moving conter-clockwise rotation
const int angle3 = 1468;      //Variable for "Fully Open" servo moving counter-clockwise rotation
int dest = 0;                 //Servo destination depending on sensor reading
const int spd = 15;           //Controls the speed of the servo moving between positions; 5 is slow
int prevPos = 0;              //Keep track of the angle two loops prior
int currPos = 0;              //Keep track of the angle one loop prior
int nextPos = 0;              //Keep track of the angle moving to during current loop

//PARAMETERS FOR THE VARIOUS LIGHT LEVELS THAT TRIGGER THE SERVO TO ROTATE
int lightLevel;               //The analog reading from the LDR
const int lowLight = 400;
const int midLight = 800;

int ATimer;
int BatTimer;
int SleepTime = 80;  //80 = 10 Min
int BatCheckTime = 8;  //8 = 1 Min

//WATCHDOG INTERRUPT
ISR (WDT_vect)
{
  wdt_disable();
}

void setup()
{
  //Serial.begin(9600);
  myservo.attach(servoPin);
  myservo.write(angle3, spd, true);
  pinMode(lowVLED, OUTPUT);
  pinMode(ldrPower, OUTPUT);
  doFirstRun();
}

void loop()
{
  BatteryCheckTimer();
  AwakeTimer();
  if (BatTimer == BatCheckTime)
  {
    doBattCheck();
  }

  if (ATimer == SleepTime)
  {
    doWhileAwake();
  } //End of Main loop if timer was reached

//Preparing to go to sleep

  byte old_ADCSRA = ADCSRA;                        // disable ADC //
  ADCSRA = 0;                                      // disable ADC //

  byte old_PRR = PRR;                              // disable Internal modules//
  PRR = 0xFF;                                      // disable Internal modules//

  MCUSR = 0;                                       // clear various "reset" flags// 

  // Watchdog Timer Parameters//
  WDTCSR = bit (WDCE) | bit (WDE);                 // allow changes, disable reset
  WDTCSR = bit (WDIE) | bit (WDP3) | bit (WDP0);   // set WDIE, and 8 seconds delay
  wdt_reset();                                     // pat the dog once program has executed.

  // Sleep Activation //
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);            //Sleep mode Selection//
  sleep_enable();                                  //Sleep Now//

  sleep_cpu ();                                    //CPU is now sleeping

// Once awake, code executes from this point. Once CPU wakes up do the follwoing to restore full operations
  sleep_disable();
  PRR = old_PRR;
  ADCSRA = old_ADCSRA;
}

void BatteryCheckTimer()                  
{
  BatTimer++;
}

void AwakeTimer()                  
{
  ATimer++;
}

void doFirstRun()
{
  digitalWrite(ldrPower, HIGH);
  delay(flashDelay);
  lightLevel = analogRead(ldrPin);  //Query photo cell
  digitalWrite(ldrPower, LOW);
  //Define the ranges based on how bright it is, and set corresponding servo position
  if (lightLevel <= lowLight)
  { 
    //Servo rotates to fully closed
    dest=angle1;      
    nextPos=angle1;
  }    
  else if (lightLevel > lowLight && lightLevel <= midLight)
  {     
    //Servo rotates to partially open
    dest=angle2;      
    nextPos=angle2;
  }
  else if (lightLevel > midLight)
  {       
    //servo rotates to fully open
    dest=angle3;
    nextPos=angle3;
  }
  prevPos = nextPos;
  currPos = nextPos;
  myservo.attach(servoPin);         //Connect to servo
  myservo.write(dest, spd, true);   //Move the servo to the new position
  delay(flashDelay);
  myservo.detach();                 //Detach servo to keep it from humming on strain
  ATimer = 0;
  BatTimer = 0;
}

void doBattCheck()
{
  while (sampleCount < voltageSamples)
  {
    runningTotalVoltage += analogRead(xVPin);
    sampleCount++;
    delay(10);
  }
  //The boostVoltage value is external power source dependent.  If using a 5v boost conveter
  //check the boost output voltage and then enter that number in the boostVoltage variable.
  voltage = ((runningTotalVoltage/voltageSamples) * (boostVoltage/1023));
  if (voltage >= minValert && voltage <= midValert)
  {
    while (LEDFlashCount < numFlash_Mid)
    {
      digitalWrite(lowVLED, HIGH);
      delay(flashDelay);
      digitalWrite(lowVLED, LOW);
      LEDFlashCount++;
      delay(flashDelay);
    }
  }
  else if (voltage < minValert)
  {
    while (LEDFlashCount < numFlash_Low)
    {
      digitalWrite(lowVLED, HIGH);
      delay(flashDelay);
      digitalWrite(lowVLED, LOW);
      LEDFlashCount++;
      delay(flashDelay);
    }
  }
  sampleCount = 0;
  runningTotalVoltage = 0;
  LEDFlashCount = 0;
  BatTimer = 0;
}

void doWhileAwake()
{
  digitalWrite(ldrPower, HIGH);
  delay(flashDelay);
  lightLevel = analogRead(ldrPin);  //Query photo cell
  digitalWrite(ldrPower, LOW);
  //Define the ranges based on how bright it is, and set corresponding servo position
  if (lightLevel <= lowLight)
  { 
    //Servo rotates to fully closed
    dest=angle1;      
    nextPos=angle1;
  }    
  else if (lightLevel > lowLight && lightLevel <= midLight)
  {     
    //Servo rotates to partially open
    dest=angle2;      
    nextPos=angle2;
  }
  else if (lightLevel > midLight)
  {       
    //servo rotates to fully open
    dest=angle3;
    nextPos=angle3;
  } 
  //IF the photocell reading is different from last sample then execute servo controls
  if (nextPos == prevPos && nextPos != currPos)
  {
    prevPos = currPos;
    ATimer = 0;
  }
  else if (nextPos != prevPos && nextPos != currPos)
  {
    prevPos = currPos;
    currPos = nextPos;  //Remember angle so we can compare it again next round
    pos_change();
  }
  else
  {
    prevPos = currPos;
    ATimer = 0;     
  }
}

//This code moves the servo to the desired position
void pos_change()
{    
  myservo.attach(servoPin);         //Connect to servo
  myservo.write(dest, spd, true);   //Move the servo to the new position
  delay(flashDelay);
  myservo.detach();                 //Detach servo to keep it from humming on strain
  ATimer = 0;
}

So, I hooked up a multi-meter and set it to monitor current draw. I watched it for 30 minutes and below are the results.

  1. Constant current draw is 26.8 microamps.
  2. Every minute the sketch wakes up and quickly checks the battery voltage. This process takes less than a seconds and the current draw increases to 38.8 microamps.
  3. Every ten minutes the sketch wakes up and checks the LDR sensor. If the sensor reading is different from the last reading the servo moves to a new preset angle. When this happens the current draw increases to about 11 milliamps. I forced a servo rotation a couple of times during this time period so that I could measure the current draw. However, during the course of a normal day, I would expect the servo to rotate no more than four times (when the sun is rising, when the sun is at its highest level, when the sun is setting, and finally when the sun has completely set).

For the last 12 days I have been running the project from a single 18650 3.7v LiPo battery. When I started the battery was at 4.12v. Battery reading today is about 3.68v. Seems to me that the project is draining the battery faster than it should be. I drained the battery and then fully charged it before starting the test so I know the battery is good. I used an inline power bank capacity tester to measure the battery capacity during charge and when it was done it registered 2405 mah.

Here are the components of this project:

  1. Arduino pro mini with on board LEDs and voltage regulator removed.
  2. LDR sensor with 10k resistor
  3. LED with 220K resistor for low battery warning
  4. 5v boost converter with on board LEDs removed (used to boost the 3.7v LiPo battery to 5v; actually boosts to about 5.27v)
  5. Knock-off clone Futaba S3003 servo

Ultimately I would like to be able to power this project from a couple of 18650 batteries for 3 months or maybe 3 AA batteries for the same period of time. Based on my research and power consumption expectations, I should be able to do it. If I can't get any better power consumption out of this little think, I might hookup a small solar panel with a LiPo battery charge controller to help extend the battery life.

Any thoughts? Thanks.

  • Hello 28µA in sleep mode still quite high, have a look on this board... talk2.wisen.com.au/product-talk2-whisper-node-avr less the 4µA when sleeping and you can have the node powered by a single AA while the servo is powered by some fat batteries, like two Alkaline D.This setup will isolate the high drainage servo from the MCU and you can even monitor the all voltages. Finally, avoid rechargeable cells as the self discharge is quite high. For best results you could try the 3.6V Lithium thionyl chloride for the servos, but they're quite expensive. – Talk2 Jun 27 '16 at 1:09
1

You should measure with a power meter the consumption of the arduino in the two conditions, active and standby. Remember that batteries last only for the 80% of their total capacity. If you can't achieve better results, you should build it with an attiny85.

  • Alberto, thanks for you input. So I have those current draw measurements. In standby or power down mode the project draws 26.8 microamps. When the servo is rotating, the project draws about 11 milliamps. On any given day, the project will spend 99% of its time in standby mode and the other 1% active. So why can't I get a 2,500 mah 3.7v LiPo battery to last longer than a couple of weeks? – darksidekilo6 Jun 25 '16 at 16:11
  • With some approximation, the battery in your setup will last 20 days at its best, so it seems to respect what you saw. – Alberto Perro Jun 25 '16 at 22:06
  • Alberto, looks like your right. The darn servo current draw in idle mode is draining the battery more than I expected. Need to isolate the servo with its own power source. – darksidekilo6 Jun 27 '16 at 6:32
  • Or you should use a relay to detach the servo when in idle – Alberto Perro Jun 27 '16 at 11:13
  • I though about using a relay but I have read some posts that stay that the servo "Jumps" when powered on and moves to the manufacturer established angle before executing my sketch. I guess that isn't really a big deal as I'm just controlling the angle of windows blinds and am more concerned with battery life. What kind of relay would I use? I have used DC to AC relays before; am i looking for a DC to DC relay? – darksidekilo6 Jun 27 '16 at 11:17
1

If you look at the discharge curves of LiPo batteries, you can see the voltage will drop a bit faster when full. Then flatten out, and have a big voltage drop when they are almost empty. So I'm not sure you can tell from the 0.44V drop that the battery is being discharged faster.

If there is a problem, I think it is either the boost converters quiescent current, or the servo. Also not that because you have to convert the 3.7V up to 5V, the current coming from the battery will have to increase by 1.35 times (at 100% efficiency).

What booster are you using? How do you turn off the servo?

PS1; I always use this calculator to get an approximate battery life.

PS2; I'd connect the pull-up resistor on the LDR to a digital pin, so you can activate it only when you need to measure the light level, saving 2-5 µA.

  • thanks for the reply. I am in the process of putting together a picture that shows the wiring of the project. I believe I have already connected the pull-up resistor of the LDR to a digital pin (const int ldrPower = 8;). As far as the boost converter goes I am using one of those postage stamp size modules available from eBay. According to the seller, the quiescent current is 130 microamps. The servo is turned on and off via code right before and after an angle change. Thanks. – darksidekilo6 Jun 25 '16 at 18:38
  • Have you tried measuring the power going out of the battery, so including everything (servo, booster, and arduino)? – Gerben Jun 25 '16 at 19:05
  • just checked the battery with a multi-meter and the power coming out before the booster is 3.68v. The power coming off the booster going in to the servo and arduino is 5.27v. – darksidekilo6 Jun 25 '16 at 19:10
  • link to picture of project: s6.postimg.org/cn0okq9v5/… – darksidekilo6 Jun 25 '16 at 19:11
  • You measured voltage. What I meant was current. – Gerben Jun 26 '16 at 13:59
1

The constant draw of 26.8 μA amounts to 0.6432 mAh per day. 38.8 μA for one second per minute is an additional 0.0048 mAh per day, and a second of 11 mA draw six times per hour would be 0.44 mAh per day. This totals to 1.088 mAh per day, or 13.056 mAh in 12 days. That's less than 5% of the 2405 mAh mentioned as the LiPo's capacity. Judging from the usual discharge voltage graphs, battery voltage should still be above 4 V at that point.

If the above is correct, you might have a defective battery, or defective current measurements. You could charge the battery in question, then connect a 100KΩ–150KΩ resistor across it. As the battery discharges, measure and record its voltage a couple of times a day. If you charge up several batteries at the same time, you could set one aside as an experimental control, attach a resistor across one of them, and attach another to your circuit.

The 26.8 μA draw while in power-down sleep is higher than I'd expect [by a factor of 3 or so]. If it includes boost converter quiescent current, perhaps use a PFET to shut down the converter when the servo isn't needed. I'm supposing the Arduino is powered directly from the battery, and that you have a boost converter to get servo power. Will the servo work ok straight from the battery?

You could power the Arduino straight from one LiPo, and power the converter and/or servo from another, to make it more likely the system will degrade safely as the batteries run down.

[Edit:] Although your Pro Mini is a 5V version, since you are powering directly from the VCC pin you can run it at a lower voltage. See §30.3 Speed Grades, in spec sheet, for the “Safe Operating Area”, ie, the guaranteed-to-work voltage-vs-MHz region. From Figure 30-1, Maximum Frequency vs. VCC, 3.78 V safely supports 16 MHz operation. Running the Pro Mini at 16 MHz below that voltage is out of spec, but I'd expect it to work fine even at appreciably lower voltages.

Prescaling the system clock [see last paragraph, below] also reduces voltage requirements. As the system clock scales down, current draw scales down more than linearly. [End Edit]

When trying to reduce current draw in sleep mode, work with a sketch that just tries out various setups; that is, a sketch that doesn't read the LDR or control the servo. A simple test sketch makes it easier to figure out what is going on. You can try tests such as all irrelevant pins initialized to low outputs, or to pulled-up inputs, etc.

It looks like the code doesn't do any intensive processing. The CPU needn't run at 16 MHz while executing delays. Instead, you could prescale the system clock (ref. §10.10, System Clock Prescaler, in spec sheet) which will drop CPU current from a few milliamps to under a milliamp during delays. You'd divide your delay counts by the scale factor, and might also change ADC clock scaling (ref. §25.4, Prescaling and Conversion Timing, in spec sheet).

  • thanks for the very detailed response. Prolly gonna take me a bit to digest. One correction: The 11 mA draw only occurs maybe 4 times per day rather than 6 times per hour. Everything is being power from the one 18650 LiPo. The pro mini is the 5v variety. Picture of project: s6.postimg.org/cn0okq9v5/… – darksidekilo6 Jun 25 '16 at 18:53
  • I'm going to try to test one of my 18650 batteries using the resistor. Is the process as simple as connecting one end of the resistor to the positive lead and one end to the negative? Will any 100KΩ–150KΩ resistor do? Do I need to worry about resistor wattage rating? Thanks. – darksidekilo6 Jun 25 '16 at 19:34
  • @darksidekilo6, see edit. Note, feel free to upvote any and all useful answers. Note, almost any such resistor should be ok; power dissipation is on the order of 30 μA times 4 V, or 120 μW which is under a milliwatt. Yes, just connect it straight across the battery. Possibly use an ammeter in series, at first, to verify about 30 μA current. Note, if Pro Mini is powered straight from battery, to measure voltages above battery voltage you would need a voltage divider (two resistors in series) – James Waldby - jwpat7 Jun 25 '16 at 20:03
  • copy all on the resistor type and ability to run the 5v pro mini from VCC at less that 5v; I'm going to give it a try on the next battery test. Your posts have been very helpful and should get me closer to my answer. I have upvoted your comments but because I am a newbie to this forum, I don't have enough "wasta" yet the upvote count isn't showing. – darksidekilo6 Jun 25 '16 at 20:08
  • could you provide me with a link to the spec sheet you are referencing. The one's I have found online don't seem to match with the section references you are providing. Thanks. – darksidekilo6 Jun 26 '16 at 8:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.