I am experiencing servo buzz related to a millis() timer in the same function
Basically a I'm using writeMicroseconds and a miilis() timer to step a servo very slowly up and down. Steadiness is important for my project and the timer is making my servos very jittery.
I read that it is possible to generate your own PWM for the Servo pins but I do not understand the code, and I do not recognize the functions from the example I was looking at. I am hoping someone here can help me make sense of the example, or make me an example I could use. Either way I would appreciate the knowledge
When using the Servo library on an Arduino, a common source of servo buzz is that the interrupt-driven servo routines don't actually give a very stable output pulse. Because the AVR takes interrupts for servicing the millis() clock and other things in the Arduino runtime, the jitter in the Servo library is on the order of several microseconds, which translates to a lot of movement in the servo.
The fix for this is to write your own pulse. Something like this:
cli();
long start = micros();
digitalWrite(PIN, HIGH);
while (micros() - start < duration)
;
digitalWrite(PIN, LOW);
sei();
This will turn off other interrupts, and generate a much cleaner PWM pulse. However, it will make the "millis() timer miss some clock ticks. (The "micros()" function may be called something else -- I forget exactly what.)
I don't understand what the posted code will achieve. I made a servo run recently from the hardware timer. Here is example code:
/*
Servo motor controlled by Timer 1
Author: Nick Gammon
Date: 1 February 2015
Note: Servo motors expect a pulse every 20 mS (50 Hz).
The pulse width should be 1.5 mS for a neutral position,
1 mS for -45 degrees and 2 mS for +45 degrees.
Some motors might expect different amounts, eg. 0.5 mS for -180
degrees and 2.5 mS for +180 degrees.
*/
const byte potpin = A0; // analog pin used to connect the potentiometer
const unsigned long PRESCALER = 8; // Timer 1 prescaler
const float PULSE_PERIOD = 0.020; // 20 mS
const float ZERO_POSITION_WIDTH = 0.0005; // 0.5 mS
const float FULL_POSITION_WIDTH = 0.0024; // 2.5 mS
// how far apart the pulses are
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
// minimum pulse width (-45 degrees)
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
// minimum pulse width (+45 degrees)
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;
void setup()
{
TCCR1A = 0; // disable all PWM on Timer1 whilst we set it up
ICR1 = PULSE_WIDTH_COUNT - 1; // frequency is every 20ms (zero-relative)
// Configure timer 1 for Fast PWM mode using ICR1, with 8x prescaling
TCCR1A = bit (WGM11);
TCCR1B = bit (WGM13) | bit (WGM12) | bit (CS11); // fast PWM top at ICR1
TCCR1A |= bit (COM1A1); // Clear OC1A/OC1B on Compare Match,
pinMode (9, OUTPUT);
} // end of setup
void loop()
{
int val = analogRead(potpin); // reads the value of the potentiometer (value between 0 and 1023)
OCR1A = ZERO_POSITION_COUNT + (val * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
delay(15); // wait for the servo to get there
} // end of loop
Basically by changing OCR1A to some different value as per the calculation near the end, you can change the servo position. That particular line assumes that the limits of travel (val) are 0 to 1023.
This should reduce or eliminate buzz because it does not rely on interrupts. If that doesn't help (and maybe try my test code first with a potentiometer, or even just a fixed value) then you must have a hardware issue.
Nick, thanks so much for your input. I have tested the servos and all the hardware involved individually, and have narrowed it down to definitely be some sort of programming issue. I originally saw heavy buzz when my LCD screen updated, and consistent buzz throughout the program. I took all the elements out but the millis() and the servos and they still jitter so it MUST be the millis() function (in my opinion)
I will attach the code that I have written to control my project (it is a camera slider +pan +tilt) it uses case/switch for the menus - but the final case statement is used to actually run the slider program (once the user has input all the parameters) Could you take a look at the bottom of the code and tell me if there is something you could help me with to eliminate the noise??
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>
#include <Adafruit_MotorShield.h> //LIBRARY FOR MOTOR DRIVER SHIELD
#include "utility/Adafruit_PWMServoDriver.h"
#include <Servo.h> //LIBRARY FOR SERVOS
Adafruit_MotorShield AFMS = Adafruit_MotorShield(0x61);
Adafruit_StepperMotor *myStepper = AFMS.getStepper(200, 2);
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
#define WHITE 0x7
Servo pan;
Servo tilt;//DECLARE SERVOS
int yy = 0; //STEP COUNTER
//declares ints
uint8_t i=0; //BUTTON INPUTS
int x = 1; //case statement movement
int input_stage = 0;
int time = millis(); //time from when the thing turned on
//user inputs
int total_hour = 0;
int total_minute = 0;
int slide_min = 0;
int slide_max = 0;
int pan_min = 800 ;
int pan_max = 2200;
int tilt_min = 800;
int tilt_max = 2200;
//Integers used for calculations for later
int total_steps = yy;
long total_time = 0;
long step_duration = 0;
int total_pan = 0;
int total_tilt = 0;
long pan_duration = 0;
long tilt_duration = 0;
long step_millis = 0;
long pan_millis = 0;
long tilt_millis = 0;
int step_percent = 0;
// Integers for real time program sliding action
int cur_step = 0;
int pan_pos = 0;
int tilt_pos = 0;
long run_dur = 0;
long start_time = 0;
int total_slide = 0;
long display_refresh = millis();
long display_dur = 1000;
int d = 1;
long end_time = millis();
int m = 0;
void setup(){
lcd.begin(16, 2);
lcd.setBacklight(WHITE);
lcd.print("Welcome to LapsR");//initialize
delay(2000);
AFMS.begin(); //initialize motor shield
pan.attach(10); //attach servos to pins
tilt.attach(9);
myStepper->setSpeed(30); // set speed
lcd.setCursor(0, 0);
lcd.print("By Doug E FresH!");
delay(2500);
lcd.setCursor(0, 1);
lcd.print("Thanks to: Dave");
delay(2000);
lcd.setCursor(0, 1);
lcd.print("Thanks to: Max");
delay(2000);
pan.writeMicroseconds(1500); //set pan and tilt servos to centre ish
tilt.writeMicroseconds(1500);
delay(5000);
pan.writeMicroseconds(1000); //Jitter Test
tilt.writeMicroseconds(1000);
delay(5000);
pan.writeMicroseconds(2000); //Jitter Test
tilt.writeMicroseconds(2000);
delay(5000);
}
void loop(){
uint8_t buttons = lcd.readButtons(); //read lcd buttons
lcd.blink(); //set cursor to blink
int limit1 = 4; //declare limit switch
pinMode(limit1, INPUT_PULLUP); // sets pin mode to button
//begin case statement
switch(x){
case 1:
if(m==0){ //move slider backwards until it finds a limit switch
if((digitalRead(limit1) == HIGH)){ // digital read reads the limit switch pin - HIGH means the switch is open
myStepper->step(2,BACKWARD,DOUBLE);
lcd.clear();
lcd.print("stage 1");
delay(10);
}
else{ //move to the next step
myStepper->release(); //release motor
m = 1; //set program to 2nd stage
}
}
if(m==1){ // moving ahead jus a bit so there is some pad for innacuracy
yy = 50; //step counter is initialized
lcd.clear();
lcd.print("stage 2");
delay(1000);
myStepper->step(50,FORWARD,SINGLE); //move forward 50 steps
m = 2;//move to the next step
}
if(m==2){ //stage 3 dawg
if((digitalRead(limit1) == HIGH)){//count steps til limit is hit
myStepper->step(1,FORWARD,SINGLE);//move forward 1 step
lcd.clear();
lcd.print("stage 3");
yy++; //step counter move up one
delay(10);
}
else{ //display step percentage and total steps
x=2;
lcd.clear();
lcd.print("step %");lcd.print((yy-100)/100);
delay(5000);
lcd.clear();
lcd.print("total: ");lcd.print(yy-100);
delay(5000);
myStepper->step((yy - 50),BACKWARD,SINGLE); // reset to start position (minus 50 steps for pad)
myStepper->release(); // release motor
step_percent = (yy-100)/100; //make calculations
total_steps = yy-100;
}
}
break;
case 2:
if(input_stage == 0){ //enter duration
if(d==1){
lcd.clear();
lcd.print("Total Duration?");//start asking for inputs - start with total overall duration hours
lcd.setCursor(0, 1);
lcd.print("H: ");
lcd.print(total_hour);
lcd.print(" M: ");
lcd.print(total_minute);
lcd.setCursor(3,1);
display_refresh = millis();
d = 0;
}
if (buttons & BUTTON_UP) { //sets hour up - slight debounce delay
total_hour = total_hour++;
delay(250);
d=1;
}
if (buttons & BUTTON_DOWN) { //sets hour down
total_hour = total_hour--;
delay(250);
d = 1;
}
if (buttons & BUTTON_SELECT){
input_stage++;delay(250); //select button moves to next stage
lcd.clear();
d=1;
}
}
if(input_stage == 1){
uint8_t buttons = lcd.readButtons();
if(d==1){ //when d == 1 update display
lcd.clear();
lcd.print("Total Duration?");//ask for minutes
lcd.setCursor(0, 1);
lcd.print("H: ");
lcd.print(total_hour);
lcd.print(" M: ");
lcd.print(total_minute);
lcd.setCursor(9,1);
d=0;
}
if (buttons & BUTTON_UP) {
total_minute = total_minute++; //set minutes
delay(100);
d=1;
break;
case 3:
//calculate moving time durations
total_time = total_hour*60;
total_time = total_time + total_minute;
total_time = total_time*60000;
total_slide = slide_max - slide_min;
step_duration = total_time/(total_slide*step_percent);
total_pan = pan_max - pan_min;
total_tilt = tilt_max - tilt_min;
pan_duration = total_time/total_pan;
tilt_duration = total_time/total_tilt;
//sets pedestal to user input slide min position
myStepper->step((step_percent*slide_min), FORWARD, DOUBLE);
x = 4;
start_time = millis();
end_time = millis() + total_time;
break;
case 4:
long remaining = end_time - millis(); //make a couple of run time calculations
run_dur = millis() - start_time;
if(millis() - step_millis > step_duration){ //take one step after step duration
myStepper->step(1, FORWARD, SINGLE);
cur_step++;
step_millis = millis(); //resets step duration
myStepper->release();
}
if(millis() - pan_millis > pan_duration){ //same as slide
pan_pos++;
pan.writeMicroseconds(pan_pos);
pan_millis = millis();
}
if(millis() - tilt_millis > tilt_duration){//same as slide
tilt_pos++;
tilt.writeMicroseconds(tilt_pos);
tilt_millis = millis();
}
if(run_dur > total_time){ //resets to top of user input once total duration runs out
x = 0;
input_stage = 0;
myStepper->step(cur_step, BACKWARD, DOUBLE); //sets motor back to home position
myStepper->release(); //release motor
}
}
}
thanks Nick, I am going to try and adapt it, but there are so many functions I do not recognize in there. I was hoping you might be able to help me apply it to the servo movement in 'case 4'. I'm sorry for being so needy! I am a big time novice, but I am dedicated to learning about robotics and engineering. If you have instagram you can actually look at some pictures and videos of the project in question if you look up @freshanator
There is so much stuff there I can't be confident any change I make will work without testing, and right now I have to strip my office because of work being done in it tomorrow. I can't be sure if Timer 1 is being used for something else or not.
Hi, good, what value are the two blue electros and where are they connected?
What spec is the servo and how many?
Have you written a simple sketch using a Servo library?
Tom......
Why so many questions, I can't help it, its my job, I fix stuff.