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.
- Constant current draw is 26.8 microamps.
- 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.
- 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:
- Arduino pro mini with on board LEDs and voltage regulator removed.
- LDR sensor with 10k resistor
- LED with 220K resistor for low battery warning
- 5v boost converter with on board LEDs removed (used to boost the 3.7v LiPo battery to 5v; actually boosts to about 5.27v)
- 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.