Servo sweep control question.

Hello

I've recently dug out my arduino board to have another bash at understanding the programming dark arts.

I have a servo attached and have been playing with the sweep program, changing start positions, angle of movement and speeds. All good so far.

But what I would like to do is use a button to trigger the sweep. Then when the button is not pressed the servo should return to the start position.

Thus far I think I have got a bit of a grip of this. I have used the button press example code and the sweep code together. At this point I have a sketch where should the button go High the servo will sweep continuously whilst the button is pressed.

The bit where I am not sure of to complete this sketch is the 'When button goes low' return servo to 90D.

I think all I need to do Is after the ELSE in the sketch is use the same command strings that sweep the servo but change the values to bring the servo back to 90.

Where I'm a bit confused is that My start position is 90D the the sweep goes to 130D then 50D so I'm not sure what to put at the moment.

So heres my code as it stands. The asterix line is where the move servo to 90 string will go. Is the rest of the code OK at this point?

/* Basic Digital Read
 * ------------------ 
 *
 * Sweeps servo, attached to pin 9, between 50 and 130 degrees. Else it
 * stays at, or returns to 90 degrees , when pressing a pushbutton
 * attached to pin 7. Button is held low with
 * 1K to 10K pull-down resistor.
 *
 */

#include <Servo.h> 

Servo myservo;  // create servo object to control a servo 

int pos = 90;    // variable to store the servo position 
int inPin = 7;   // choose the input pin (for a pushbutton)
int val = 0;     // variable for reading the pin status

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  pinMode(inPin, INPUT);    // declare pushbutton as input
}

void loop()
{
  val = digitalRead(inPin);  // read input value
  if (val == HIGH) {
    for(pos = 50; pos < 130; pos += 1)  // goes from 90 degrees to 180 degrees 
  {                                  // in steps of 1 degree 
    myservo.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(15);                       // waits 15ms for the servo to reach the position 
  } 
  for(pos = 130; pos>=50; pos-=1)     // goes from 180 degrees to 50 degrees 
  {                                
    myservo.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(15); 
  } else {
    ******************************************************
  }
}

Cheers Paul

I think all I need to do Is after the ELSE in the sketch is use the same command strings that sweep the servo but change the values to bring the servo back to 90.

Yes, that is all you need to do. Exactly what position you command the servo to go to when the button is not pressed is up to you but a simple myservo.write(90); will do it.

Personally I would have variables for homePosition, sweepStart and sweepEnd to make the code easier to read and maintain.

You should note that as written the program will finish its current sweep cycle before going to its home position when the button is released because of the for loops and delays. Is that what you want or do you want it to go home immediately the button is released ? If so the program will need to be changed to remove the delay()s and to allow the button state to be determined at any point.

The next step, then, is to look at the state change detection example. Make the servo start moving when the switch is pressed once. Make it stop moving when the switch is pressed again. Then start, then stop, etc.

You will, of course, need to completely rewrite the sketch to get rid of the for loops and the calls to delay(), so that you CAN read the switch in a timely fashion. Look at the blink without delay example for clues, and google Arduino + state machine. (On any given pass through loop(), there may, or may not, be a need to move the servo, and it might, or might not, be time to move the servo if there is a need to move it.)

Hello

Thanks for the advice. I had it in the back of my mind that perhaps the myservo.write(90) might be ok so that keeps it simpler :smiley:

What I will be doing is pretty simple so the delay before the last ELSE command isn't a great worry. Basically the servo will oscillate a pair of 'robot ears'. Probably a criminal waste of what the atmega328 is capable of :grin:

So the current timings may well be shorter/longer dependant on gearing or lever lengths.

The 3 variable points is interesting. I will have a look and see if I can string that together out of interest.

I think I recall buying extra 328 chips so I could make dedicated bare bones boards to use thereby saving my arduino board for future attempts at programming.

Here is the completed code

/* Servo_Sweep_with button
 * ------------------ 
 *
 * Sweeps servo, attached to pin 9, between 50 and 130 degrees. Else it
 * stays at, or returns to 90 degrees , when pressing a push button
 * attached to pin 7. Button is held low with
 * 1K to 10K pull-down resistor.
 *
 */

#include <Servo.h> 

Servo myservo;  // create servo object to control a servo 

int pos = 90;    // variable to store the servo position 
int inPin = 7;   // choose the input pin (for a pushbutton)
int val = 0;     // variable for reading the pin status

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  pinMode(inPin, INPUT);    // declare pushbutton as input
}

void loop()
{
  val = digitalRead(inPin);  // read input value
  if (val == HIGH) {
    for(pos = 50; pos < 130; pos += 1)  // goes from 90 degrees to 180 degrees 
  {                                  // in steps of 1 degree 
    myservo.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(15);                       // waits 15ms for the servo to reach the position 
  } 
  for(pos = 130; pos>=50; pos-=1)     // goes from 180 degrees to 50 degrees 
  {                                
    myservo.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(15); 
  } else {
    myservo.write(90);              // tell servo to go to position in variable 'pos' 
    delay(15); 
  }
}

Just caught your reply paulS. I had seen that and will also look at it to.

Cheers Paul

Here is the completed code

I am afraid that it does not compile. You have the else inside the if not after it.

The 3 variable points is interesting. I will have a look and see if I can string that together out of interest.

It will make no difference to the working of the program but to me

for(pos = sweepStart; pos < sweepEnd; pos += 1)  // goes from 90 degrees to 180 degrees

is easier to read and understand and you only have to change the value of the variables in one place to change the limits of the sweep. Actually
for(pos = sweepStart; pos <= sweepEnd; pos += 1)  // goes from 90 degrees to 180 degrees would be better.

NOTE: I have deliberately left your misleading comments in place.

Oh hell

My bad. This is what comes of not wearing your reading glasses :sunglasses: And hitting save rather than compile.... :fearful:

Here it is after it was compiled and found OK. Corrected the comments for the angles as well

/* Servo Sweep with Button
 * ------------------ 
 *
 * Sweeps servo, attached to pin 9, between 50 and 130 degrees. Else it
 * stays at, or returns to 90 degrees , when pressing a pushbutton
 * attached to pin 7. Button is held low with
 * 1K to 10K pull-down resistor.
 *
 */

#include <Servo.h> 

Servo myservo;  // create servo object to control a servo 

int pos = 90;    // variable to store the servo position 
int inPin = 7;   // choose the input pin (for a pushbutton)
int val = 0;     // variable for reading the pin status

void setup() {
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
  pinMode(inPin, INPUT);    // declare pushbutton as input
}

void loop()
{
  val = digitalRead(inPin);  // read input value
  if (val == HIGH) {
    for(pos = 50; pos < 130; pos += 1)  // goes from 50 degrees to 130 degrees 
  {                                  // in steps of 1 degree 
    myservo.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(15);                       // waits 15ms for the servo to reach the position 
  } 
  for(pos = 130; pos>=50; pos-=1)     // goes from 130 degrees to 50 degrees 
  {                                
    myservo.write(pos);              // tell servo to go to position in variable 'pos' 
    delay(15); 
  }
  }
  else {
    myservo.write(90);              // tell servo to go to position in variable 'pos' 
    delay(15); 
  }
}

Hmm I like your code and as you say there is only one place then needed to change the variables. I could also have the servo delay as a variable, as they are all the same, as well.

An interesting point now. The switch will be activated by another servo...... But I presume I could attach the relevant servo channel to the arduino. Following the same convention I have at the moment which is a separate battery for the servo sharing only GND with the arduino and the signal lead. Once the arduino is programmed to listen for the change from 1.5ms (as off) to 2ms (as on)?

Cheers Paul

I have at the moment which is a separate battery for the servo sharing only GND with the arduino and the signal lead.

That's the right way to do it.

Once the arduino is programmed to listen for the change from 1.5ms (as off) to 2ms (as on)?

I am not sure what you mean by this. In general myservo.write(90) will output pulses of 1500 microSeconds and myservo.write(180) will output pulses of 2000 microSeconds. If you want to control the servo more accurately, assuming that it is mechanically able to be controlled more accurately, you can use myservo.writeMicroseconds() and output the required value (1000 to 2000) directly. Actually, if you use a value greater than 1000 in myservo.write() it will output microseconds anyway, but using the correct function is more readable.

Note that not all servos can move through 180 degrees though.

Simple servo button code that does not require a resistor. Keeping the pin high instead of low might be a little more electrical noise resistant.

//zoomkat servo button test 7-30-2011

#include <Servo.h>
int button1 = 4; //button pin, connect to ground to move servo
int press1 = 0;
Servo servo1;

void setup()
{
  pinMode(button1, INPUT);
  servo1.attach(7);
  digitalWrite(4, HIGH); //enable pullups to make pin high
}

void loop()
{
  press1 = digitalRead(button1);
  if (press1 == LOW)
  {
    servo1.write(160);
  }
  else {
    servo1.write(20);
  }
}
void setup()
{
  pinMode(button1, INPUT);
  servo1.attach(7);
  digitalWrite(4, HIGH); //enable pullups to make pin high
}

I know that you know what you are doing but in my eyes this is very clumsy.

Why name the button pin and use the name in one command and not the other and why, when the majority of users are on IDE version 1.0 or above, would you not use the much more explicit method of activating the built in pullup resistor ?

pinMode(button1, INPUT_PULLUP);

Hello there

Sorry, to clarify, I have a radio control set. So my intention is to control a model with it. The arduino is to control the 'Ear movement' on the model.

To interface the arduino/sweeping servo with the radio receiver I was going to have a servo on the radio receiver rotate and close the arduino switch to activate the sweep servo.

Now I am wondering if I can link one of the radio receiver servo channels directly to the arduino, at least the signal line to replace the mechanical switch with a signal input from the radio gear.

Hi zoomkat I had forgotten there where some internal pull ups. I'll look at this.

Cheers Paul

Now I am wondering if I can link one of the radio receiver servo channels directly to the arduino, at least the signal line to replace the mechanical switch with a signal input from the radio gear.

Indeed you can. For example

byte receiverPin = 7;
unsigned long duration;

void setup()
{
  Serial.begin(115200);
  pinMode(receiverPin, INPUT);
}

void loop()
{
  duration = pulseIn(receiverPin, HIGH);
  Serial.println(duration);
  delay(100);
}

This program will print the pulse duration of an RC channel connected to pin 7 as long as the RC and Arduino share a common ground connection. Once you have the pulse duration you can test its value and trigger events on the Arduino.

Hello

Now that is interesting to know. I will give that a bash probably tomorrow.

I've just had a fraught 30 minutes. Having wired up the servo, button and arduino. Uploaded the sketch and watched it jitter and generally not work.

Check wiring, resistor, servo, etc etc. Battery supply for the servo is going flat I think. So get 9V PSU for arduino. Yes I'm wiring the single servo direct :fearful: I'll sort out the correct method once everything is sorted.

OK no jitter now...... No response to the switch either though..... It took a little while to realise that I had forgotten the 5V line :-[ Despite the fact I was looking at the diagram on this site.....

Cheers Paul

Hello

Well I've been playing with this sketch a bit trying out a bit of coding at the end.

At this moment I have -

/* Servo Sweep with Button
 * ------------------ 
 *
 * Sweeps servo, attached to pin 9, between 50 and 130 degrees. Else it
 * stays at, or returns to 90 degrees , when pressing a pushbutton
 * attached to pin 7. Button is held low with
 * 1K to 10K pull-down resistor.
 *
 */

#include <Servo.h> 

Servo myservo;  // create servo object to control a servo 

int pos = 90;          // variable to store the initial servo position 
int homePosition = 90; // variable to store the home position
int sweepStart = 50;   // variable to store servo start position
int sweepEnd = 130;    // variable to store servo end position
int moveDelay = 6;     //variable to store delay time in ms
int inPin = 7;         // choose the input pin (for a pushbutton)
int val = 0;           // variable for reading the pin status

void setup() {
  myservo.attach(9);        // attaches the servo on pin 9 to the servo object
  pinMode(inPin, INPUT);    // declare pushbutton as input
}

void loop()
{
  val = digitalRead(inPin);  // read input value
  if (val == HIGH) {
    for(pos = sweepStart; pos <= sweepEnd; pos += 1)   // goes from n degrees to n degrees as specified in variables
  {                                                    // in steps of 1 degree 
    myservo.write(pos);                                // tell servo to go to position in variable 'pos' 
    delay(moveDelay);                                  // waits  specified ms for the servo to reach the position 
  } 
  for(pos = sweepEnd; pos >= sweepStart; pos -= 1)     // goes from n degrees to n degrees as specified in variables
  {                                                    // in steps of 1 degree
    myservo.write(pos);                                // tell servo to go to position in variable 'pos' 
    delay(moveDelay);                                  // waits  specified ms for the servo to reach the position
  }
  }
  else {
    myservo.write(homePosition);                       // tell servo to go to position in variable 'homePosition' 
    delay(moveDelay);                                  // waits  specified ms for the servo to reach the position
  }    
}

Initially I tried to set up a variable for the delay. But this seemed to cause some compiling errors... I forgot the ; :grin:

The above code is finished I think. Other than creating a variable which could be used to set the increment of degrees..... Just how far do you go? :smiley:

Next up will be trying to read the RX input to use as a switch. Thanks for the code to do that!

cheers paul

Just how far do you go?

Do away with the delay()s to enable you to read the Rx output each time through loop() ?

#include <Servo.h>
Servo myServo;

const byte servoPin = 7;
const byte buttonPin = 8;
unsigned long stepPeriod = 6;
unsigned long stepStart = 0;
unsigned long currentTime = 0;
int servoIncrement = 1;
const byte sweepStart = 50;
const byte sweepEnd = 130;
const byte servoHome = 90;
byte servoPos = servoHome;;

void setup()
{
  Serial.begin(115200);
  myServo.attach(servoPin);;
  myServo.write(servoPos);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop()
{
  if (digitalRead(buttonPin) == LOW)
  {
    currentTime = millis();
    if (currentTime - stepStart > stepPeriod)
    {
      servoPos += servoIncrement;
      if (servoPos <= sweepStart || servoPos >= sweepEnd)
      {
        servoIncrement *= -1;
      }
      stepStart = currentTime;
    }
    myServo.write(servoPos);
  }
  else
  {
    myServo.write(servoHome);
  }
                       //read the Rx pin here each time through loop()
}

Another possible improvement would be to make make the sweep start when the button is pressed and released and stop when it is pressed and released again or how about varying the sweep speed depending on an input from an RC channel ?

Hello

I executed the first pulsein/serial output sketch.

minimum PPM/PWM is 1600 at full right deflection
middle PPM/PWM is 1900 at rest
maximum PPM/PWM is 2600 at full left deflection

These figures are slightly higher than the norm for radio control systems but the unit is highly configurable at the software level. Servos also work correctly on it with these settings I can confirm.

I have also tried the last sketch. At the moment this causes the servo to sweep without my hand on the transmitter. I may be missing something of course.....

For clarity the radio control set is the CheapControlSystem, the C12C model. Using a PS2 wireless handset to interface with the board (I reckon it is an arduino based device) it works very well to. See link.

C12C radio control

I bought this 2 years ago and have only just got it up and running due to real life getting in the way :angry:

Any hoo - Your code. What is the function of the '||' (pipe?) I have only seen this once before in a calculation for parallel resistors.

With the delays() absent does this mean the sweeping speed is controlled by the amount of deflection at the control stick? I see it -> stepPeriod = 6

Reading the code several times am I right in thinking I need to add the values, found using the first sketch, to the startStep/currentTime? so the later sketch knows what the trigger level is?

cheers paul

What is the function of the '||' (pipe?)

The double pipe || is the logical OR comparison symbol so the servo changes direction if it reaches the lower limit OR the upper limit.

I have also tried the last sketch. At the moment this causes the servo to sweep without my hand on the transmitter. I may be missing something of course.....

How is the switch wired ? Note that I used INPUT_PULLUP in the pinMode() for the button pin. This turns on the internal pullup resistor keeping the input normally HIGH. My switch is wired to take the switch pin LOW when pressed. Using a pullup (or pulldown) resistor ensures that the input is never floating at an unknown voltage,

minimum PPM/PWM is 1600 at full right deflection
middle PPM/PWM is 1900 at rest
maximum PPM/PWM is 2600 at full left deflection

These figures are slightly higher than the norm for radio control systems but the unit is highly configurable at the software level. Servos also work correctly on it with these settings I can confirm.

As long as the servos work that's what matters. As you suggest, you can use the numbers from your test program in the real one. Check for greater than, less than or a range to trigger the action(s) that you want. On my quadcopter, for instance, I have a 3 position switch triggering 3 different LED flashing patterns.

Hello there

When I saw the line with the, what I now know as OR, in it I could see the line itself was a way of reducing the original 2 line method of sweeping. Thanks for the clarification.

I read the code and missed the input was now low. I should have realised anyway seeing as the internal PULL_UP had been activated so logically the trigger level has to be low :-[

So for me at this moment what I am seeking to do is use a PPM/PWM input to trigger a digital HIGH to switch on the servo sweep routine.

The neutral variable on this setup would 1900. (there was a slight variation in the value up to about an additional 40-55ms) So set it at say 1960ms. Then all there is to do is use the IF input > variable neutral -- runs the sweep code ending with ELSE for when the input is within the neutral.

Thinking, out loud as it were, could a second servo, connected to a separate pin be controlled by the same input going IF input < neutral -- move in this direction until --?

cheers paul

Then all there is to do is use the IF input > variable neutral -- runs the sweep code ending with ELSE for when the input is within the neutral.

Yes, it is easy as that.

could a second servo, connected to a separate pin be controlled by the same input going IF input < neutral -- move in this direction until --?

Yes. Using millis() for timing and thus making the loop() function free running, means that you can test inputs and change outputs as often as you like.

Hello there

Still mulling this over so in the meantime I correctly setup the switch for a LOW detection and reran the code with the || in it.

:money_mouth_face: Seriously smart programming. The loss of the delay function seriously speeds things up and I assume the || function helps with the immediacy of the response to the switch being released.

On the previous sketch the sweep cycle had to complete regardless of the button being released at any point.

Now the instant the button is released, at any point in the sweep, the servo goes to 'home position' until the button is pressed, released or held.

I'm from the ZX-81 generation and I never got the hang of programming then. The only thing I ever managed was a program that generated a random number that I had 10 goes to guess or the 'go kart' crashed. The kart being a couple of letters that moved down screen for each successive wrong guess.

Other than that one thing I never got to grips with it. I could read a basic program and understand what it would do but that was all. A bit like cotton, something I'm quite prepared to wear, but have no idea how it works ;D

I fear I may not get far with programming now but I'll have a bash!

I've been reading lots of blogs on RC interfacing with the arduino. Some of it may sink in but it makes your head spin!

You mentioned your quadcopter? Is there a thread, blog for it?

cheers paul

You mentioned your quadcopter? Is there a thread, blog for it?

No, just me building and flying it, no blog or thread.

To complicate matters a little from the point of view of using the RC channels to control things via the Arduino the Rx uses CPPM to pass the received channels to the flight controller so instead of 6 signal wires plus 5V and GND there is only one signal wire plus 5V and GND and the control signals are sent serially rather than in parallel. This makes for much neater installation on the quad and I found a library for decoding the serial control signals so all is well.