How I solved a problem with servo jitter while driving a short WS2812 string

So I'm rather new to Arduino, but it seemed like a great way to re-introduce myself to the electronic projects I loved as a EE student. As is often the case, I finished school and put my lab kit and tools on the shelf to gather dust because my job requires me to physically build absolutely nothing . :frowning:

But here I am with two little boys and an electric train set that needs some crossing gates and flashing lights. So I ordered up some parts and started tinkering. I settled on the WS2812 LEDs because they're small and addressable. Having RGB capability is neat but not really applicable to this project (though I'm considering some kind of Easter egg mode for my boys to discover). At this point I'm starting small and have 1 servo-enabled crossing gate, 2 WS2812 chips chained together, and a momentary push button to indicate when the train is approaching the crossing (this will be replaced with a reed switch and train-mounted magnet, but I'm still writing the code and working on a breadboard).

After creating some code that basically followed the servo library examples and NeoPixel library examples, I was left with a working system with one annoying flaw: the servo was jittery at 90°. With only 1 servo and 2 LEDs (set to brightness 32) I didn't think I would have issue running straight off my RedBoard, but after I saw the jitter power was the first thing I looked at. I added a 1000 ?F electrolytic cap across my power rails; that produced no change. I thought maybe my servo was bad so I tried three different servos; they all behaved the same. I tried a different Arduino board; no change. I thought maybe the momentary switch was noisy so I pulled it altogether; no change. Finally I commented out all the momentary switch code and just stepped through the two modes with a short delay between, and that finally solved the issue. So at that point I thought it had something to do with input. That turned out to be a red herring and I wasted some time inverting the logic of my input (to no effect).

Then I took a broader view. I basically have two states: train crossing or cars crossing. When cars are crossing you do nothing with the gate (leave it at 90, which is where the train crossing state left it and leave the signal lights off). In fact all I had in that block of code was set LED 0 to off (0, 0, 0), set LED 1 to off (0, 0, 0), and write the data to the string. I thought that perhaps looping through this code continuously might not be the best idea, so I added a 20ms delay. That helped immensely but did not completely solve the jittery servo. The continuous jitter was gone but it would still jump chronically, at a regular rate. I increased this delay to 100ms. That made the chronic jumps occur further apart. I thought maybe I should tell the servo to go to 90° in this function (even though it was already there) but that didn't help either.

Finally I dug into the full documentation of the servo library (I know...RTFM). Therein I saw the detach function. I decided that it would be a good idea to detach the servo in a function where I wasn't using it (cars crossing) and then attach it in a function where I was (train crossing). That solved my issue completely. There was no more jitter even with the push button in the circuit (and in the code).

Because I couldn't find anything useful when Googling for a solution I thought I would take the time to write this up. So if you have a "noisy servo with input", "noisy PWM output with input button", or just a "jittery servo" maybe this post will help you. Also if you have some wisdom to explain this behavior I'd love to learn where I went wrong. I suspect my fix is a hack and not necessarily the best solution. In any case thanks for reading my lengthy first post.

Thanks for that.
It is not clear exactly what is going on, for help how about posting your code either using code tags or an attachment.
It sounds like you might have some sort of interaction with the libaries you used.

Here's the code:

#include <Adafruit_NeoPixel.h>
#include <Servo.h>

/*
Railroad signal light plus servo for crossing gate.  Momentary button for control.
Will be expanded to two crossings plus reed switch (and magnet) for automatic state switching.

Rail crossing LEDs - NeoPixels
pin 3

Servo
pin 5

Momentary on button
pin 2

*/


#define LED_Pin 3
Adafruit_NeoPixel strip = Adafruit_NeoPixel(2, LED_Pin, NEO_GRB + NEO_KHZ800);

Servo servo1;  // servo control object

int Button_Pin = 2;

void setup()
{
  strip.begin();
  strip.setBrightness(32);
  strip.show(); // Initialize all pixels to 'off'

  servo1.attach(9);
  servo1.write(90); // not sure if this is necessary, but it won't hurt

  pinMode(Button_Pin,INPUT);
}

void loop()
{
  int button_state = digitalRead(Button_Pin);

  if (button_state == LOW) {
    cars_crossing();
  }

  if (button_state == HIGH) {
    servo1.attach(9); // necessary because the cars_crossing detaches servo1
    lower_gate();
    int count;
    for (count = 0; count <= 2; count++)
    {
      train_crossing();
    }
    raise_gate();
  }
}

void train_crossing()
{
  strip.setPixelColor(0, 255, 0, 0);
  strip.setPixelColor(1, 0, 0, 0);
  strip.show();
  delay (500);
  strip.setPixelColor(0, 0, 0, 0);
  strip.setPixelColor(1, 255, 0, 0);
  strip.show();
  delay (500);
  strip.setPixelColor(0, 0, 0, 0); // signals off
  strip.setPixelColor(1, 0, 0, 0); // signals off
  strip.show();
}

void cars_crossing()
{
  delay(100); // initially added to help with servo jitter at 90°
  if (servo1.attached()){
    servo1.detach(); // this is what cleared up the jitter completely
  }
  strip.setPixelColor(0, 0, 0, 0); // signals off
  strip.setPixelColor(1, 0, 0, 0); // signals off
  strip.show();
}

void lower_gate()
{
  int angle;
  for (angle = 90; angle <= 180; angle++)
    {
      servo1.write(angle);
      switch(angle) { // this is to get the lights flashing while the gate is moving
      case 90 : //At starting point, turn on LED 0
        strip.setPixelColor(0, 255, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.show();
        break;
      case 113 : //At ~22.5°, switch to LED 1
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 255, 0, 0);
        strip.show();
        break;
      case 135 : //At 45°, switch to LED 0
        strip.setPixelColor(0, 255, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.show();
        break;
      case 157 : //At ~67.5°, switch to LED 1
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 255, 0, 0);
        strip.show();
        break;
      }
      delay (22);  //delay of 22ms creates a total sweep time of 1980ms, which is approx divisible by 500 ms that each LED is on in the trains_crossing function
    }
}

void raise_gate()  //works the same as raise_gate, but backwards
{
  int angle;
  for (angle = 180; angle >= 90; angle--)
    {
      servo1.write(angle);
            switch(angle) {
      case 180 :
        strip.setPixelColor(0, 255, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.show();
        break;
      case 157 :
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 255, 0, 0);
        strip.show();
        break;
      case 133 :
        strip.setPixelColor(0, 255, 0, 0);
        strip.setPixelColor(1, 0, 0, 0);
        strip.show();
        break;
      case 110 :
        strip.setPixelColor(0, 0, 0, 0);
        strip.setPixelColor(1, 255, 0, 0);
        strip.show();
        break;
      }
      delay (22);
    }
}

Hi ! I am new to Arduino myself but from what I read and experienced so far, my guess is an issue with timers. Anyways... thanks for your workaround. It just made my day :slight_smile:

These days their are boards that generate a jitter free signal for the servos you can use instead of the software servo driver.
The problem is that the Neopixels need a precisely timed signal and so do the servos. The Neopixel library turns off the interrupts to get this leaving the servos to jitter because the pulses are not generated at the correct time.

See the Adafruit web site for a partial workaround, they also sell the servo boards.