Servo motor controlled by Timer 1 to prevent jitter

Hi all, I've found the above code in an old 2015 thread (hence why I started a new one):

/*
Servo motor controlled by Timer 1

Author: Nick Gammon
Date: 1 February 2015
*/

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.4 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, It does use Timer1 in order to "solve" the jitter problem that could affect a servo when no command changes are issued to it.

Could this be modified in order to control 2 servos with 2 different inputs for a pan/tilt setup?

The regular Servo library uses Timer1 AFAIK. Have you tried it?

...R

Robin2:
The regular Servo library uses Timer1 AFAIK. Have you tried it?

...R

Thank you for your answer Robin2; yes, I've tryied it and as of now I've noticed 2 big differences:

  • If I use the library, on startup the servo goes to mid position (1500ms I guess); with this code it doesn't, but it keeps working perfectly if I don't move the joystick (at least it seem so);
  • If I use the servo library, when I let the joystick in a position, even if in the serial monitor I get a very steady value, I can hear it buzzing and twiching.

I'll try to be more precise: I made a low pass filter for the joystick, I use an external precision analog reference and by using the averaging example provided with the IDE, I get very very stable values. When I map those values for the servo using the servo library, the values obtained are still rock solid, but in reality I can hear the servos trying to move (using write or writemicroseconds doesn't make any difference).

I've never been able to reduce this "noise", but when I uploaded this code everything became absolutely perfect.

I would really love to understand what's going on! :smiley:

You can do a Servo.write() before doing Servo.attach() so that it starts in a position of your choice.

Have you compared the code in the Servo library with Nick Gammon's code to see where they differ.?
...R

Robin2:
You can do a Servo.write() before doing Servo.attach() so that it starts in a position of your choice.

Have you compared the code in the Servo library with Nick Gammon's code to see where they differ.?
...R

Write before attach does "set" a position, however there's still a bit of a 1-2 degrees movement (and it start buzzing as before).

This is a breaf description given by NickGammon (at the time) about the first post code:

"...basically by changing OCR1A to some different value as per the calculation near the end, the servo change 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."

And it indeed does!

I'm sure that a better servo would dramatically increase the motion quality too; I'm using a Turnigy TGY-778MG at the moment (perfect size).

Regardless of the use of the library, what I'm really curious about is how to replicate the commands issued in the setup() and in the loop() to the servo on another capable pin (hence another analog input and another servo output).

Currently I'm on an ATTiny841 and I would use PA6 and PB2 (OC1A, already implemented in the code in the first post, and OC2A)

Is that viable? If yes, could somebody help me or guide me in the process?

When you say OC1A and OC2A I presume you would need to use Timer1 and Timer2

I have been looking at the 841 datasheet but I can't immediately figure out which physical pins those refer to.

...R

You are right!

I use the ATTinyCore; the image below of the ATTiny841 pinout state that PA6 is OC1A and PB2 is OC2A.

Those two pins would be perfect (PA6 is already working)

Of course, if needed, I could use another capable pin instead of PB2.

Your image is different from the 841 datasheet that I have and I note that it shows OC1A on two different pins and also OC0B on two pins. I would nomally expect SpenceKonde's images to be reliable but there is room for doubt in this case. He masquerades on this Forum as DrAZZY so you could probably send him a PM.

Alternatively, just write some code to cause the Timer to toggle a pin at a very slow rate and connect an LED to the different pins until you find which one is being used.

Assuming you want to run two servos it would be easier if they both use the same Timer. Then you just need to change the count for each half of the Timer to vary the PWM width (I think).

...R

Robin2:
Assuming you want to run two servos it would be easier if they both use the same Timer. Then you just need to change the count for each half of the Timer to vary the PWM width (I think).

...R

You're right! I'll try to send him a PM so he may check this topic and figure out what's going on.

const unsigned long PRESCALER = 8;
const float PULSE_PERIOD = 0.020;
const float ZERO_POSITION_WIDTH = 0.0005;
const float FULL_POSITION_WIDTH = 0.0024;
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

void setup()
{
  TCCR1A = 0;
  TCCR2A = 0;
  ICR1 = PULSE_WIDTH_COUNT - 1;
  ICR2 = PULSE_WIDTH_COUNT - 1;
  TCCR1A = bit (WGM11);
  TCCR2A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);
  TCCR2B = bit (WGM13) | bit (WGM12) |  bit (CS11);
  TCCR1A |= bit (COM1A1);
  TCCR2A |= bit (COM1A1);

  pinMode (2, OUTPUT);
  pinMode (4, OUTPUT);
}

void loop()
{
  analogRead(A1);
  OCR1A = ZERO_POSITION_COUNT + (average * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  analogRead(A2);
  OCR2A = ZERO_POSITION_COUNT + (average1 * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
}

That's the code that uses OC1A (tested, works on pin 4, PA6) and OC2A (not tested yet, should work on pin 2, PB2) and reads analog values from A1(PA1) and A2(PA2) (but this should not be relevant).

It seems ok but I'm not really sure about the setup part for OC2A (I'll be honest, I've just copy/pasted the commands for OC1A and replaced it with OC2A).

The mappings on that pinout diagram are bogus, because it's a version from I think like a year ago... Where are you getting it? Is it on a site I control? If so, I need to update it.

See the latest version here

The default mappings that ATTinyCore uses for the output compare pins on the timers are:

#define CORE_OC0A_PIN  PIN_A4 // TOCC3 
#define CORE_OC0B_PIN  PIN_A5 // TOCC4 
#define CORE_OC1A_PIN  PIN_A6 // TOCC5
#define CORE_OC1B_PIN  PIN_A3 // TOCC2
#define CORE_OC2A_PIN  PIN_B2 // TOCC7
#define CORE_OC2B_PIN  PIN_A7 // TOCC6

At the detailed hardware level of programming you are doing there is NO role for float variables. They just do not exist in the Atmega hardware. Do all your maths with integers of an appropriate size.

And the code in Reply #9 is incomplete.

...R

DrAzzy:
The default mappings that ATTinyCore uses for the output compare pins on the timers are:

#define CORE_OC0A_PIN  PIN_A4 // TOCC3

Does PIN_A4 mean bit 4 in Port A?

...R

Robin2:
Does PIN_A4 mean bit 4 in Port A?

...R

Yes.

It's also shown on the corrected pinout chart linked to in that post.

DrAzzy:
The mappings on that pinout diagram are bogus, because it's a version from I think like a year ago... Where are you getting it? Is it on a site I control? If so, I need to update it.

See the latest version here
ATTinyCore/avr/extras/ATtiny_x41.md at OldMaster---DO-NOT-SUBMIT-PRs-here-Use-2.0.0-dev · SpenceKonde/ATTinyCore · GitHub

The default mappings that ATTinyCore uses for the output compare pins on the timers are:

#define CORE_OC0A_PIN  PIN_A4 // TOCC3 

#define CORE_OC0B_PIN  PIN_A5 // TOCC4
#define CORE_OC1A_PIN  PIN_A6 // TOCC5
#define CORE_OC1B_PIN  PIN_A3 // TOCC2
#define CORE_OC2A_PIN  PIN_B2 // TOCC7
#define CORE_OC2B_PIN  PIN_A7 // TOCC6

if I remember correctly I got it from the Tindie page (it'sthe first pinout diagram if you type "ATTiny841" on google images)!

Thank you for your answer!

Robin2:
At the detailed hardware level of programming you are doing there is NO role for float variables. They just do not exist in the Atmega hardware. Do all your maths with integers of an appropriate size.

And the code in Reply #9 is incomplete.

...R

You're right, I thought so. In fact, I feel like I have no control at all on the maths in such a code.

Do you refer to "CONST FLOAT" in the header? Or to analog readings in the loop?
In the loop I guess that the code below would limit the values to integers.

int value1 = 0;
int value2 = 0;

void setup() {
}

void loop() {
  value1 = analogRead(A1);
  value2 = analogRead(A2);
}

Also, what do you mean with "incomplete"?

Thank you very much!

c0rsa1r:
Also, what do you mean with "incomplete"?

Have you tried compiling it?

You have two variables average and average1 that were never defined. And, because of their names I suspect there is more than the definition missing.

In the code in Reply #9 there are a few float contants - I incorrectly referred to them as variables in Reply #11

...R

Robin2:
Have you tried compiling it?

You have two variables average and average1 that were never defined. And, because of their names I suspect there is more than the definition missing.

In the code in Reply #9 there are a few float contants - I incorrectly referred to them as variables in Reply #11

...R

my bad, actually I was about to implement an average and forgot to complete the code.

const unsigned long PRESCALER = 8;
const float PULSE_PERIOD = 0.020;
const float ZERO_POSITION_WIDTH = 0.0005;
const float FULL_POSITION_WIDTH = 0.0024;
const unsigned long PULSE_WIDTH_COUNT = F_CPU / PRESCALER * PULSE_PERIOD;
const unsigned long ZERO_POSITION_COUNT = F_CPU / PRESCALER * ZERO_POSITION_WIDTH;
const unsigned long FULL_POSITION_COUNT = F_CPU / PRESCALER * FULL_POSITION_WIDTH;

const int numReadings = 64;

int readings[numReadings];
int readIndex = 0;
int total = 0;
int average = 0;

int readings1[numReadings];
int readIndex1 = 0;
int total1 = 0;
int average1 = 0;

void setup()
{
  TCCR1A = 0;
  ICR1 = PULSE_WIDTH_COUNT - 1;
  TCCR1A = bit (WGM11);
  TCCR1B = bit (WGM13) | bit (WGM12) |  bit (CS11);
  TCCR1A |= bit (COM1A1);

  TCCR2A = 0;  //not sure of this part (from here)
  ICR2 = PULSE_WIDTH_COUNT - 1;
  TCCR2A = bit (WGM21);
  TCCR2B = bit (WGM23) | bit (WGM22) |  bit (CS21);
  TCCR2A |= bit (COM2A1);  //to there

  analogReference(EXTERNAL);
  pinMode (2, OUTPUT);
  pinMode (4, OUTPUT);
}

void loop()
{
  getValues();
  OCR1A = ZERO_POSITION_COUNT + (average * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
  OCR2A = ZERO_POSITION_COUNT + (average1 * (FULL_POSITION_COUNT - ZERO_POSITION_COUNT) / 1024) - 1;
}

void getValues()
{
  total = total - readings[readIndex];
  readings[readIndex] = analogRead(A1);
  total = total + readings[readIndex];
  readIndex = readIndex + 1;
  if (readIndex >= numReadings) {
    readIndex = 0;
  }
  average = total / numReadings;

  total1 = total1 - readings1[readIndex1];
  readings1[readIndex1] = analogRead(A2);
  total1 = total1 + readings1[readIndex1];
  readIndex1 = readIndex1 + 1;
  if (readIndex1 >= numReadings) {
    readIndex1 = 0;
  }
  average1 = total1 / numReadings;
}

This does compile, but I've not yet tryied to upload it because of the setup "not sure" part.

c0rsa1r:
This does compile, but I've not yet tryied to upload it because of the setup "not sure" part.

It still has float constants.

Print the values they produce and I reckon you will see that they are not working as you think they are.

...R

Robin2:
It still has float constants.

Print the values they produce and I reckon you will see that they are not working as you think they are.

...R

Wow, I wasn't expecting that. It's just a mess of random integers and that doesn't really make much sense.

How am I supposed to replace those float? With integers?

Thank you!

c0rsa1r:
How am I supposed to replace those float? With integers?

That's what I would do.

Rather than nnn * 0.005 you can use nnn * 5 / 1000

When you are working with integer maths you need to ensure that intermediate values don't overflow or underflow the datatype they are in. Sometimes you need to do calculations with long variables even though the final answer will fit in a int.

...R