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?
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!
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?
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).
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.
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:
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.
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.
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.
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.