Controlling a servo with an LDR

Hi everyone, I am new to this forum and to Arduino and to C++ :frowning: but I am really looking forward to learning more and becoming more involved here as it is something I have wanted to do for quite some time!

I am currently working on a project that would open and close my blinds depending on the amount of light outside. I hate sleeping with the blinds open but love waking up to light coming in through the windows.

The problem I am having is mainly due to my total lack of experience but I am hoping this project is simple yet complicated enough to be a good intro for me (I have already done all of the simple tutorials like blink and such). The part I am currently working on is seemingly very simple although I am having trouble executing it and I have plans to add more complicated functionality later on (like multiple modes, bluetooth smartphone control and an indoor facing motion sensor).

I am currently going off of various beginnings of tutorials and forum posts from a variety of sources and don't have much knowledge of the platform. I most definitely look forward to learning more about this summer, which I plan on dedicating to learning C++ and Arduino.

Anyway, on to the project =>>> ((nothing above this really matters to this problem, sorry))

I am currently using an Arduino UNO R3, a LDR, a 10K resistor and a FeeTech (FS5103R) continuously rotating servo motor. LINK TO SERVO SPECS

The setup so far is very simple and these are the schematics I followed to set up the circuit (except I used Pin 11 for the servo signal pin):

I had the code to the point where the servo would turn one way when it was exposed to light and the other when I blocked the light with my hand but it was always continuously rotating at different speeds depending upon the amount of light unless I blocked just the right amount of light to write a "90" to the servo signal pin. Although this is kind of mesmerizing and entertaining, it isn't what I was shooting for.

The goal is:

  • Get the servo to rotate 3 full rotations one direction when the LDR senses light (sunrise)
  • Wait for the light to go away (sunset)
  • Turn 3 full rotations in the other direction.
    (3 full rotations is how many times you need to turn the rod on blinds to open/close them)

I have tried to modify the code to make the servo only turn one way or the other way (at one constant speed) depending on the amount of light hitting the LDR but it isn't working.

Here is the code I started with from an Instructables article:

#include <Servo.h>

Servo myservo;
int val;

void setup()
{
myservo.attach(12);
}
void loop()
{
val = analogRead(0);
val = map(val, 0, 1023, 0, 179);
myservo.write(val);
delay(15);
}

And here is what I am currently working with. Again, I based it off of the above code but made some changes to var names and such to help make it less confusing and also attempted the if else statements. I do not see why this doesn't work. I added comments to the code the best I could.

#include <Servo.h>

Servo myservo;            //name servo
int photocellPin = 0;     //define photo cell reading input
int photocellReading;     //define photo cell reading variable

void setup()
{
  Serial.begin(9600);     //initiate serial @ 9600 baud
  myservo.attach(11);     //define pin 11 as servo signal pin
}
void loop()
{
  Serial.print("Brightness = ");                //print "Brightness = "
  Serial.println(photocellReading);             //print the photocell reading
  photocellReading = analogRead(photocellPin);                    //define photocellReading as pin 0 input from LDR
  photocellReading = map(photocellReading, 0, 1023, 0, 179);      //map the LDR input to a value between 1-180 so the servo can understand it
{
  if (photocellReading <= 89);         //if the LDR is showing less than half light intensity
    myservo.write(50);                //then tell the servo to rotate backwards at a steady rate
  else (photocellReading >= 91)      //if the LDR is showing more than half light intensity
    myservo.write(130);               //then tell the servo to rotate forwards at a steady rate

  delay(15);
}
}

So I know this is probably a super simple solution but I am hoping to get some pointers and help so that I will be able to learn by example. I plan to keep this thread open until the project is done (with all the fancy features) no matter how long it takes so it might be an interesting learning log on top of helping out an aspiring maker.

Thanks in advance for your time and sorry this post was so long winded!

Regards,
Sky

else (photocellReading <= 89); <--------------<<<<< get rid of the ; and you want 'else if'

lol I knew it was something simple! Thank you! Now I know to not use semicolons at the end of the if else statements.

Next step is to make it only turn 1080 degrees then wait for the light to change again. Any pointers for that one? I have found examples of how to do it with a normal 180 degree servo by monitoring its position but I'm not sure how to do it with this continuously rotating one.

Thanks again.
Sky

If it rotates continuously you will need additional input to detect how far it has rotated
look at KY-040 rotary encoder module Example code

Your Arduino is not going to last long if you continue to power the servo directly from it. A servo capable of moving blinds will take way more current than the voltage regulator in the Arduino is able to supply.

zhomeslice:
If it rotates continuously you will need additional input to detect how far it has rotated
look at KY-040 rotary encoder module Example code

Thanks for your reply. There is no way to simply tell it to rotate for a certain period of time? Opening and closing the shade doesn't need to be exactly 1080 degrees, just close to it. So if I timed the servo and made adjustments, how would I add a delay? I tried adding a delay so it would turn for 2.5 seconds but I keep getting a compilation error.

Also, I added a new else if to make the servo not go either direction when not being told something by the other else statements.

Here is my code:

#include <Servo.h>

Servo myservo;            //name servo
int photocellPin = 0;     //define photo cell reading input
int photocellReading;     //define photo cell reading variable

void setup()
{
  Serial.begin(9600);     //initiate serial @ 9600 baud
  myservo.attach(11);     //define pin 11 as servo signal pin
}
void loop()
{
  Serial.print("Brightness = ");                //print "Brightness = "
  Serial.println(photocellReading);             //print the photocell reading
  photocellReading = analogRead(photocellPin);                    //define photocellReading as pin 0 input from LDR
  photocellReading = map(photocellReading, 0, 1023, 0, 179);      //map the LDR input to a value between 1-180 so the servo can understand it
{
  if (photocellReading <= 70)                    //if the LDR is showing darkness
    myservo.write(110);                          //then tell the servo to rotate forwards at a steady rate (close shade)
      delay(2500);                               //2.5 second delay while shade is closing?
      myservo.write(90);                         //stop rotation after dalay
  else if (photocellReading >= 110)              //if the LDR is showing light
    myservo.write(70);                           //then tell the servo to rotate backwards at a steady rate (open shade)
      delay(2500);                               //2.5 second delay while shade is opening?
      myservo.write(90);                         //stop rotation after dalay
  else if (photocellReading >= 71 && photocellReading <= 109)
    myservo.write(90);
    
  delay(15);
}
}

UKHeliBob:
Your Arduino is not going to last long if you continue to power the servo directly from it. A servo capable of moving blinds will take way more current than the voltage regulator in the Arduino is able to supply.

Noted. Thanks for the heads up, hope I haven't done any damage already. I don't think so since the servo hasn't been under any load (nothing attached to the arms). I'll use a separate battery pack for the servo or maybe split off the 9V Arduino battery pack with a buck converter to bring it down to 5V

    if (photocellReading <= 70)                    //if the LDR is showing darkness
      myservo.write(110);                          //then tell the servo to rotate forwards at a steady rate (close shade)
    delay(2500);                               //2.5 second delay while shade is closing?
    myservo.write(90);                         //stop rotation after dalay
    else if (photocellReading >= 110)              //if the LDR is showing light

Where is the end of the code that is executed when the if test returns true ?
You have code between the end of the if and the else if. That is what is causing the error in that area.

So if I timed the servo and made adjustments

Since you are presuming all conditions to be EXACTLY the same every time the servo is energized, you will probably be disappointed because all conditions will not be the same. The servo will not turn the same amount during a specific time unless the conditions are EXACTLY the same. What may be the simplest way to control the amount the servo is moved to open or close the blinds is to use limit switches.

  • Scotty

I am looking forward to hearing what happens when the servo, powered from the Arduino if the circuit posted is to be believed, stalls at the end of its travel and its current requirements rise alarmingly.

Thank you for your replies

UKHeliBob:

    if (photocellReading <= 70)                    //if the LDR is showing darkness

myservo.write(110);                          //then tell the servo to rotate forwards at a steady rate (close shade)
   delay(2500);                               //2.5 second delay while shade is closing?
   myservo.write(90);                         //stop rotation after dalay
   else if (photocellReading >= 110)              //if the LDR is showing light



Where is the end of the code that is executed when the if test returns true ?
You have code between the end of the if and the else if. That is what is causing the error in that area.

I was hoping if the statement was true, the motor would turn while the delay occured (2.5 seconds) and then it would end when the motor received the value telling it to stop. I am obviously on the wrong track here. Could you possible provide and more specific pointers?

I already kind of knew it was the code in between the if and else statements causing the problem i just don't know how to go about adding code without interrupting those statements.

scottyjr:
Since you are presuming all conditions to be EXACTLY the same every time the servo is energized, you will probably be disappointed because all conditions will not be the same. The servo will not turn the same amount during a specific time unless the conditions are EXACTLY the same. What may be the simplest way to control the amount the servo is moved to open or close the blinds is to use limit switches.

  • Scotty

I am presuming the conditions will be very close to the same, not exactly. Therefore I do not expect the servo to turn the same amount each time but it doesn't need to be exact.

I looked into limit switches but there is simply nothing to toggle the switch.

Just for the heck of it even though you don't think it will work well, could you help me out with programming it to only turn for a set amount of time? I would like to try than even if I have to move to a different method afterwards.

UKHeliBob:
I am looking forward to hearing what happens when the servo, powered from the Arduino if the circuit posted is to be believed, stalls at the end of its travel and its current requirements rise alarmingly.

Since your last comment I have decided to change to not powering the servo from the Uno. I would like to either have a separate 6V battery pack for it or maybe to save space use the 9V Arduino battery pack and THIS buck converter to step the 9V down to 5V. I think that would solve the problem.

Here is the schematic I was thinking of following:

I was hoping if the statement was true, the motor would turn while the delay occured (2.5 seconds) and then it would end when the motor received the value telling it to stop. I am obviously on the wrong track here. Could you possible provide and more specific pointers?

If you do not use braces to enclose a block of code after an if then only the next line of code will be conditionally executed. So

    if (photocellReading <= 70)                    //if the LDR is showing darkness
      myservo.write(110);                          //then tell the servo to rotate forwards at a steady rate (close shade)
    delay(2500);                               //2.5 second delay while shade is closing?
    myservo.write(90);                         //stop rotation after dalay

will write 110 to the servo when the test is true but will always execute the delay() and write 90 to the servo.
Whereas

    if (photocellReading <= 70)                    //if the LDR is showing darkness
      {
         myservo.write(110);                          //then tell the servo to rotate forwards at a steady rate (close shade)
         delay(2500);                               //2.5 second delay while shade is closing?
         myservo.write(90);                         //stop rotation after dalay
      }

will execute all three lines of code only when the condition is true. The else clause will then also follow the if immediately and eliminate at least one of the errors that you were getting.

NOTE : whilst the delay() is happening no other code will run so it is not always the best choice for doing something for a period of time and other techniques (using millis() for timing) are available.

Just for the heck of it even though you don't think it will work well, could you help me out with programming it to only turn for a set amount of time? I would like to try than even if I have to move to a different method afterwards.

You've already incorporated that in your code:

delay(2500);                               //2.5 second delay while shade is closing?

That delay method will suffice since, as is, your code is very short and simple. However, it is generally frowned upon and in a great deal of cases it cannot successfully be used. For example, suppose you did decide to use limit switches. During the delay your Arduino is out to lunch. Anything you would like your Arduino to do, such as monitoring the limit switches, will have to wait until lunch is over. Delay is a blocking code.

I think you may have a problem in choosing to open or close the blinds simply based on a threshold value. Natural light is not linear. I imagine that the light upon your photocell will hover around your threshold and will sometimes be at, above or below your threshold until such time that, without deviation, it meets the requirement to satisfy the 'if'. Perhaps a better way would be to mark the time that the threshold is met and come back 10 minutes later and see if it is still met. If so, yep, it's dark or it's light, go ahead and work dem blinds.

In your code you are continually reading the photocell and based on it's value, tell the servo to rotate for 2.5 seconds and tell it to stop. Then next time through the loop you do the same thing. So unless the photocell value does not meet the 'if' statement threshold parameter. it's gonna keep rotating. You need a flag that says 'whoa, we already did this'. Perhaps two flags named 'open' and 'closed'. Now your if statement could be 'if (theshold met AND not already open (or closed depending on the desired task)){drive the servo for 2.5 seconds}

P.S. You do not need a servo to test this code. The on-board led will sufice.

  • Scotty

UKHeliBob:
If you do not use braces to enclose a block of code after an if then only the next line of code will be conditionally executed.

Ah, that was it! Solved all my errors, thank you so much. I had a feeling I was using those curly brackets wrong or not enough.

UKHeliBob:
NOTE : whilst the delay() is happening no other code will run so it is not always the best choice for doing something for a period of time and other techniques (using millis() for timing) are available.

scottyjr:
That delay method will suffice since, as is, your code is very short and simple. However, it is generally frowned upon and in a great deal of cases it cannot successfully be used. For example, suppose you did decide to use limit switches. During the delay your Arduino is out to lunch. Anything you would like your Arduino to do, such as monitoring the limit switches, will have to wait until lunch is over. Delay is a blocking code.

Interesting, thanks for the heads up. I will look into using other timing methods.

scottyjr:
I think you may have a problem in choosing to open or close the blinds simply based on a threshold value. Natural light is not linear. I imagine that the light upon your photocell will hover around your threshold and will sometimes be at, above or below your threshold until such time that, without deviation, it meets the requirement to satisfy the 'if'. Perhaps a better way would be to mark the time that the threshold is met and come back 10 minutes later and see if it is still met. If so, yep, it's dark or it's light, go ahead and work dem blinds.

Yes this totally makes sense. Like if the sun was setting and the amount of light was just above the threshold and a cloud went in from of the sun for a minute (or any variation like you said) the blinds would keep opening and closing until the moment of no more deviation.

It sounds like a job for the millis() timing function so I will definitely look into that.

scottyjr:
In your code you are continually reading the photocell and based on it's value, tell the servo to rotate for 2.5 seconds and tell it to stop. Then next time through the loop you do the same thing. So unless the photocell value does not meet the 'if' statement threshold parameter. it's gonna keep rotating. You need a flag that says 'whoa, we already did this'. Perhaps two flags named 'open' and 'closed'. Now your if statement could be 'if (theshold met AND not already open (or closed depending on the desired task)){drive the servo for 2.5 seconds}

I believe I was able to get these flags set up because I was able to get the code right where I wanted it! It now when the light value crosses the threshold the servo turns one was for 2.5 seconds then stops until the light value has crossed the other threshold then it turns for 2.5 seconds the other way before stopping. Those additions also prevent the blind in general from trying to close or open when it is already closed or open, which would probably break the device.

It would seem that all that is left for this basic part of the project is to figure out the timing function and implement some code that will re-check the reading some time after the first check to avoid quick switching back and forth. I'm open to tips tricks or pointers about this function and I will be back after reading up on it some.

Here is my updated sketch:

#include <Servo.h>

Servo myservo;                                                    //name servo
int photocellPin = 0;                                             //define photo cell reading input
int photocellReading;                                             //define photo cell reading variable
int shadeOpen = 0;
int shadeClosed = 0;


void setup()
{
  Serial.begin(9600);                                             //initiate serial @ 9600 baud
  myservo.attach(11);                                             //define pin 11 as servo signal pin
}
void loop()
{
  Serial.print("Brightness = ");                                  //print "Brightness = "
  Serial.println(photocellReading);                               //print the photocell reading
  
  photocellReading = analogRead(photocellPin);                    //define photocellReading as pin 0 input from LDR
  photocellReading = map(photocellReading, 0, 1023, 0, 179);      //map the LDR input to a value between 1-180 so the servo can understand it
{
  if (photocellReading <= 70 && shadeClosed == 0)                 //if the LDR is showing darkness and the shade isn't closed already
    {
      myservo.write(110);                                         //then tell the servo to rotate forwards at a steady rate (close shade)
      delay(2500);                                                //2.5 second delay while shade is closing
      myservo.write(90);                                          //stop rotation after dalay
      shadeClosed = 1;                                            //set shadeClosed var as true
      shadeOpen = 0;                                              //set shadeOpen var as false
    }
  else if (photocellReading >= 110 && shadeOpen == 0)             //if the LDR is showing light and the shade isn't open already
    {
      myservo.write(70);                                          //then tell the servo to rotate backwards at a steady rate (open shade)
      delay(2500);                                                //2.5 second delay while shade is opening
      myservo.write(90);                                          //stop rotation after dalay
      shadeOpen = 1;                                              //set shadeOpen var as true
      shadeClosed = 0;                                            //set shadeClosed var as false
    }
  else if (photocellReading >= 71 && photocellReading <= 109)     //if the LDR senses neither new light or new dark (mid-day)
    {
      myservo.write(90);                                          //stop servo
    }
    
  delay(15);
  
}
}

Thanks again for your help!

The code is all set for now until I add additional features!

I do have a question about my power source though. I wanted to test the device on a real set of blinds but don't want to burn out the Uno by powering the servo from it. Since the buck converter wont come in until sometime next week, I grabbed a 4 AA battery holder from radioshack because I thought AA batteries were 1.5V each, making the pack 6V perfect for the servo.

However, I found that each of my Duracell AAs reads just over 2V and the battery pack with all 4 batteries in reads well over 8V instead of 6V. Is this normal? I looked online and can't find anything about AA batteries reading 2V. All the pages I found say they should test near 1.5V. I don't want to fry my servo so I wanted to consult the forum before connecting it up. Thanks!

P.S. - My multimeter is of the 5 year old $15 variety so could that be the problem? It is also reading the 9V battery pack that came with the arduino as over 12V so I'm thinking it is my multimeter...

  • Sky

EDIT: I attached pictures of my readings

Just wanted to say that the explanations and results here were exactly what I was looking for to help me with a similar project.

Thanks to OP, and to all the helpful replies!