Unstable PWM Outputs to Servos

Hi. I'm using an original UNO to drive head tilt on a scarecrow Halloween prop. I'm seeing unexpected "chatter-like" movement that correlates with seemingly random PWM pulse interruptions (scope shows signal remains high between what should be 20 ms pulse intervals)... as if it's randomly dropping pulse cycles.

I'm using .write() to position the servos. I'm using millis() for timing (no interrupts). The suspect switch case is "sc_ChangePosition. I'm struggling to understand how unintended values could be written to the servos, or how the PWM signals could be interfered with.

Any advice would be appreciated.

/***********************************************************
*
* Scarecrow
* Version 1.2
* July 18, 2025
* 
*
* This sketch drives two scapula-mounted (left & right) servo motors controlling the scarecrow's 
* head tilt. Every performance cycle, the eyes light up, random values are generated for the
* left servo's position, the right servo's position, the movement period, and the movemeent delay.
*
* The movement of both servos is executed such that they span the calculated duration.
*
************************************************************/

#include<Servo.h>

const byte leftServoMin = 20;      // Minimum allowed left servo value (degrees)
const byte leftServoMax = 160;    // Maximum allowed left servo value (degrees)
const byte rightServoMin = 20;     // Minimum allowed right servo value (degrees)
const byte rightServoMax = 160;   // Maximum allowed right servo value (degrees)
const unsigned int periodMin = 500;  // Minimum allowed period in milliseconds
const unsigned int periodMax = 5000; // Maximum allowed period in milliseconds
const unsigned int delayMin = 500;   // Minimum allowed delay in milliseconds
const unsigned int delayMax = 20000; // Maximum allowed delay in milliseconds
const byte maxRedEyesLevel = 194; // Maximum red LED PWM level
const byte maxGrnEyesLevel = 255; // Maximum green LED PWM level
const byte maxBluEyesLevel = 37;  // Maximum blue LED PWM level
const byte eyeChangeInterval = 10;   // Interval between brightness changes in ms

unsigned int period;              // Duration of the next position change in ms
byte redEyesLevel = 0;            // Initialize red eye LED PWM level (analogWrite())
byte grnEyesLevel = 0;            // Initialize green eye LED PWM level
byte bluEyesLevel = 0;            // Initialize blue eye LED PWM level

const byte leftServoPin = 5;      // Left servo driven by Pin 5 PWM
const byte rightServoPin = 6;     // Right servo driven by Pin 6 PWM
const byte redEyesPin = 9;        // Red eyes LED on Pin 9 PWM
const byte grnEyesPin = 10;       // Green eyes LED on Pin 10 PWM
const byte bluEyesPin = 11;       // Blue eyes LED on Pin 11 PWM

const byte sc_CalcParameters = 1; // Calculate next performance cycle's parameters & delay
const byte sc_BrightenEyes = 2;   // Brighten eyes
const byte sc_BrighterEyeDelay = 3; // Delay between eye brightening intervals
const byte sc_ChangePosition = 4; // Move to next position
const byte sc_DimEyes = 5;        // Dim eyes
const byte sc_DimmerEyeDelay = 6; // Delay between eye dimming intervals
const byte sc_Delay = 7;          // Delay until next performance cycle

byte currentLeftServoPosition = leftServoMin; // Initialize the current left servo position variable
byte currentRightServoPosition = rightServoMax; // Initialize the current right servo position variable
byte nextLeftServoPosition = 0;   // The next left servo position variable
byte nextRightServoPosition = 0;  // The next right servo position variable
char leftServoIncrement;          // 1 if increasing position, -1 if decreasing position
char rightServoIncrement;         // 1 if increasing position, -1 if decreasing position
int leftServoDisplacement;        // = currentLeftServoPosition - nextLeftServoPosition
int rightServoDisplacement;       // = currentrighttServoPosition - nextRightServoPosition
byte leftPositionClocks;          // Number of required position change clocks/stops
int leftPositionIncrementPeriod;  // Time in ms for each position change clock
byte rightPositionClocks;         // Number of required position change clocks/stops
int rightPositionIncrementPeriod; // Time in ms for each position change clock
bool leftPositionChangeCompleted = false; // Variables to track position change completion status
bool rightPositionChangeCompleted = false;
unsigned int nextDelay;           // milliseconds to next position change
unsigned long MillisMoment;       // Variable for measuring time via millis()
byte sc_StepNo = sc_CalcParameters; // Initialize switch pointer to calculate parameters     

Servo leftServo;                  // Name of scarecrow's left servo
Servo rightServo;                 // Name of scarecrow's right servo

void setup()
{
  // put your setup code here, to run once:
  leftServo.attach(leftServoPin); // Attach left servo to its assigned PWM pin
  rightServo.attach(rightServoPin); // Attach right servo to its assigned PWM pin
  pinMode(redEyesPin, OUTPUT);    // Set red LED driver pin to OUTPUT
  pinMode(grnEyesPin, OUTPUT);    // Set green LED driver pin to OUTPUT
  pinMode(bluEyesPin, OUTPUT);    // Set blue LED driver pin to OUTPUT
  leftServo.write(leftServoMin);  // Rotate to initial servo positions
  rightServo.write(rightServoMax); // Servos rotate in opposite directions wrt linkage
  currentLeftServoPosition = leftServoMin; // Iniitalize current servo positions
  currentRightServoPosition = rightServoMax;
}

void loop()
{
  // put your main code here, to run repeatedly:
  switch (sc_StepNo)
  {  
    case sc_CalcParameters:
       leftPositionChangeCompleted = false;  // Flag that the next position changes haven't been completed
       rightPositionChangeCompleted = false;
       nextLeftServoPosition = random(leftServoMin, leftServoMax); // The next left servo position
       nextRightServoPosition = random(rightServoMin, rightServoMax); // The next right servo position
       period = random(periodMin, periodMax); // Duration of next position change
       nextDelay = random(delayMin, delayMax);

       leftServoDisplacement = currentLeftServoPosition - nextLeftServoPosition;
       if (leftServoDisplacement > 0)     // current - next determines if position is increasing or decreasing
       {
          leftServoIncrement = -1;
       }
       else
       {
          leftServoIncrement = 1;
       }
       leftPositionClocks = abs(leftServoDisplacement);
       if (leftPositionClocks == 0) ++leftPositionClocks;
       leftPositionIncrementPeriod = period/leftPositionClocks;

       rightServoDisplacement = currentRightServoPosition - nextRightServoPosition;
       if (rightServoDisplacement > 0)
       {
          rightServoIncrement = -1;
       }
       else
       {
          rightServoIncrement = 1;
       }
       rightPositionClocks = abs(rightServoDisplacement);
       if (rightPositionClocks == 0) ++rightPositionClocks;
       rightPositionIncrementPeriod = period/rightPositionClocks;

       sc_StepNo = sc_BrightenEyes;
       break;

    case sc_BrightenEyes:
       if (redEyesLevel < maxRedEyesLevel)        // Increment any sub-max LED levels and write to their LEDs
       {
          ++redEyesLevel;
          analogWrite(redEyesPin, redEyesLevel);
       }
       if (grnEyesLevel < maxGrnEyesLevel)
       {
          ++grnEyesLevel;
          analogWrite(grnEyesPin, grnEyesLevel);
       }
       if (bluEyesLevel < maxBluEyesLevel)
       {
          ++bluEyesLevel;
          analogWrite(bluEyesPin, bluEyesLevel);
       }
       MillisMoment = millis();                   // Mark the current value of millis()
       sc_StepNo = sc_BrighterEyeDelay;           // Go to next state to measure elapsed time. LEDs at maximum?
       break;

    case sc_BrighterEyeDelay:
       if ((millis() - MillisMoment) >= eyeChangeInterval)  // If eye change interval has elapsed, check for maximized LEDS
       {
          if ((redEyesLevel == maxRedEyesLevel) && (grnEyesLevel == maxGrnEyesLevel) && (bluEyesLevel == maxBluEyesLevel))
          {
            MillisMoment = millis();
            sc_StepNo = sc_ChangePosition; // If LEDs are all at their maximum brightness, jump forward to next state
          }
          else
          {
            sc_StepNo = sc_BrightenEyes;   // Otherwise, loop back to continue brightening the eyes
          }
       }
       break;

    case sc_ChangePosition:    
       if ((millis() - MillisMoment) >= leftPositionIncrementPeriod)
       {
          if (currentLeftServoPosition != nextLeftServoPosition)
          {
            currentLeftServoPosition += leftServoIncrement;
            leftServo.write(currentLeftServoPosition);
          }
          else
          {
            leftPositionChangeCompleted = true;
          }    
       }
       if ((millis() - MillisMoment) >= rightPositionIncrementPeriod)
       {
          if (currentRightServoPosition != nextRightServoPosition)
          {
            currentRightServoPosition += rightServoIncrement;
            rightServo.write(currentRightServoPosition);
          }
          else
          {
            rightPositionChangeCompleted = true;
          }    
       }
       if (leftPositionChangeCompleted && rightPositionChangeCompleted)
       {
          sc_StepNo = sc_DimEyes;
       }
       break;

    case sc_DimEyes:
       if (redEyesLevel > 0)                      // Decrement any above 0 LED levels and write to their LEDs
       {
          --redEyesLevel;
          analogWrite(redEyesPin, redEyesLevel);
       }
       if (grnEyesLevel > 0)
       {
          --grnEyesLevel;
          analogWrite(grnEyesPin, grnEyesLevel);
       }
       if (bluEyesLevel > 0)
       {
          --bluEyesLevel;
          analogWrite(bluEyesPin, bluEyesLevel);
       }
       MillisMoment = millis();          // Mark the current value of millis()
       sc_StepNo = sc_DimmerEyeDelay;    // Go to next state to measure elapsed time. LEDs at 0?
       break;

    case sc_DimmerEyeDelay:
       if ((millis() - MillisMoment) >= eyeChangeInterval)  // If eye change interval has elapsed, check for all LEDS at 0
       {
          if ((redEyesLevel == 0) && (grnEyesLevel == 0) && (bluEyesLevel == 0))
          {
            MillisMoment = millis();
            sc_StepNo = sc_Delay;       // If LEDs are all at 0, jump forward to next state
          }
          else
          {
            sc_StepNo = sc_DimEyes;     // Otherwise, loop back to continue dimming the eyes
          }
       }
       break;

    case sc_Delay:
       if ((millis() - MillisMoment) > nextDelay)
       {
          sc_StepNo = sc_CalcParameters; // Initialize Step Number at calculating parameters
       }
       break;
  }    
}

The servo library uses timer 1 on an UNO, so you cannot use analogWrite() for pins 9 and 10 to drive the LEDs at the same time.

The Servo library can drive servos on any digital pin, it does not need to be a PWM capable pin. Even the analog pins are usable, since they can also be used as digital pins.

@hallowed31 will be interested in the project, and maybe practical information.

As a matter of interest, where are the servos getting their power from ?

Now... that's extremely helpful information that I would never have thought of. Thank you!

The servos receive power from a 6V power supply which is separate from the Arduino supply.

Since the 6V is “separate”, is there a common ground between the - side of the 6V source and the Arduino ground? There must be.

Can servo.h make any DIO pin act like a PWM pin? Does TIMER1 interfere when not on the same PORT?

The Servo library can use any digital I/O pin, it generates the servo signal using an interrupt driven by the timer, not the hardware PWM. On an UNO, timer 1 is used by the Servo library, so you cannot use hardware PWM on pins 9 or 10 since timer 1 would be used by the hardware PWM on those pins.

yes, and port is irrelevant. Servo.h uses the pin number you give it when you attach(), and the timer interrupt simply generates an appropriate pulse when the pin is “attached”.

edit - I see david said more or less the same thing, more succinctly. Funny, his post wasn’t there when I clicked on reply two minutes ago, yet his post appears to have been made 13 minutes ago. More discourse stupidity, I guess.

Have you tried a sketch where just the servo section is working as intended?

I think it's best to use type long for random anything in Arduino. See here:
https://docs.arduino.cc/language-reference/en/functions/random-numbers/random/

Also, I'm not sure that you're random number generator is doing what you think it is. You might seed your random number with millis() or how the link I posted shows to do it.

I like to use a trick to save on servos and get less jitter, helping extend the life of the servo. I like to call

  leftServo.attach(leftServoPin); // Attach left servo to its assigned PWM pin
  rightServo.attach(rightServoPin); 

immediately before using the servo, provided the mechanical design is not such that the servo is constantly holding something in place (servo loses all holding power if unattached).

Then, as soon as the servo angles are changed, use

  leftServo.detach(); // Attach left servo to its assigned PWM pin
  rightServo.detach(); 

This also gives you the flexibility to detach one servo at a time, such as the case if your head turning mechanism uses two servos in a push-pull type of arrangement, so they aren't fighting each other.