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;
}
}