tiny85 + servo puzzle

I can't help posting on this - it has me bamboozled.

I tested the adafruit "SoftServo" library on an ATtiny85 last night, and found that with 'write()' values from 0 to 180, I was only getting very slightly over 90 degrees swing, rather than 180 degrees.

Thinking the servo might have been at fault, I wrote a quickie for a UNO, using the standard "Servo" library, and the servo swings the full 180 degrees without a problem.

Next, to fully isolate the problem, I wrote my own short program for the tiny85 with no library, using timer-based 50Hz generation and 'delayMicroseconds()' for pulse timing, to generate 1000uS, 1500uS and 2000uS pulses. No good, I was back to only a little over 90 degrees swing. I altered the pulselengths until I did get a full swing, and found that fully clockwise needed a 660uS pulse, and fully anti-clockwise required a 2560uS pulse. (Noticeably, almost double the usual 1000uS range.) These values gave me almost a full 180 degree swing, stopping just short of the servo's internal stops.

Totally intrigued by the behaviour, I set the values back to 1000uS for CW, 1500uS for mid-point and 2000uS for CCW, got a repetition of the reduced swing, so grabbed the oscilloscope to find out what was going on. The 'scope told me that the timing was great, almost a perfect 50hz, and that the pulse lengths were indeed 1000uS, 1500uS and 2000uS. Yet still the @#$% servo was only swinging about 90 degrees.

Double-checked yet again with the UNO and "Servo" library, and '0', '180' have the servo happily swinging a full 180 degrees.

This isn't a project as such, just a good little puzzle that I'd like to understand. If anyone can make any suggestions I'd be pleased to hear them, (before I go crazy).

The ATtiny85 fuses are set for 4.3V BOD, 8MHz internal oscillator, and this is the code:-

// Defines:-
// Original values:-
#define CW_LIMIT 1000
#define MID_POINT 1500
#define CCW_LIMIT 2000

// Corrected values for full 180 degree swing:-
//#define CW_LIMIT 660
//#define MID_POINT 1610
//#define CCW_LIMIT 2560

// Pin allocations:-
const byte servoPin = 0;                  // Servo on pin D0, (tiny85 physical pin 5).

// Variables:-
volatile uint8_t counter = 0;
int pulseLength;

void setup()
{
    timerSetup();
    pinMode(servoPin, OUTPUT);
    pulseLength = MID_POINT;
    delay(1000);
}

void loop()
{
    pulseLength = CCW_LIMIT;
    delay(1000);
    pulseLength = MID_POINT;
    delay(1000);
    pulseLength = CW_LIMIT;
    delay(1000);
    pulseLength = MID_POINT;
    delay(1000);
}

void timerSetup()    // Set up the interrupt that will refresh the servo:-
{
    OCR0A = 0xAF;                       // Any number is OK.
    TIMSK |= _BV(OCIE0A);               // Enable the interrupt.
}

void refreshServo()
{
    digitalWrite(servoPin, HIGH);
    delayMicroseconds(pulseLength);
    digitalWrite(servoPin, LOW);
}

// Timer ISR:-
SIGNAL(TIMER0_COMPA_vect)               // Called every 2ms.
{
    counter += 2;
    if (counter >= 20)                  // Refresh the servo every 20ms.
    {
        refreshServo();
        counter = 0;
    }
}

I wasn't sure whether to consider this a microcontroller problem, or a motor problem, but since it is a tiny, decided to post in this section.

Have the UNO and the T85 send the same value out then check the signal on your scope. Are they the same?

What voltage is the tiny putting out?

Paul__B: What voltage is the tiny putting out?

A nice square 5V signal, Paul.

justone: Have the UNO and the T85 send the same value out then check the signal on your scope. Are they the same?

Yes and no. :) Now I'm even more confused, but you've led me to the source of the problem. Because the servo was doing a full swing using the UNO with the "Servo" library and 'write()', with '0', '90' and '180', I didn't look at it on the 'scope until right now.

I just checked the UNO using 'write()', with '0', '90' and '180', and the pulses range from about 500us to about 2500us wide, giving the full swing of about 180 degrees.

I also wrote a UNO version using 'writeMicroseconds()', with '1000', '1500' and '2000', and the pulses range from 1000us to 2000us as expected, but the servo only swings 90 degrees.

So what's going on here? A standard RC servo is supposed to take pulses from 1000us to 2000us, and move through 180 degrees, yet when that range is sent the servo can't move full swing. When 500us to 2500us is sent, the servo can move full swing. To quote Professor Julius Sumner Miller, "Why Is It So?"

Edit: And the adafruit "SoftServo" library did the same as me - 1000us to 2000us pulses using 'delayMicroseconds()', giving 90 degrees movement.

Edit2: From Wikipedia:-

Generally the minimum pulse will be about 1 ms wide and the maximum pulse will be 2 ms wide.

From here:- Servo Control

I just did a bit more digging. The reference for the standard "Servo" library's 'writeMicroseconds()' says this:-

Description

Writes a value in microseconds (uS) to the servo, controlling the shaft accordingly. On a standard servo, this will set the angle of the shaft. On standard servos a parameter value of 1000 is fully counter-clockwise, 2000 is fully clockwise, and 1500 is in the middle.

Note that some manufactures do not follow this standard very closely so that servos often respond to values between 700 and 2300.

So it seems that when using 'write()', the library defaults to 500us and 2500us limits, to allow for the 700us and 2300us mentioned above. My servo must just be a bit further out-of-spec, needing 660us to 2560us for a full swing. And I note that they have the values backward - '1000' should be fully clockwise, not anti-clockwise, and '2000' should be fully anti-clockwise. (Looking down on the shaft from the top.)

Looking further, inside the "Servo" library in "Servo.h", these are defined:-

#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo  
#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo 
#define DEFAULT_PULSE_WIDTH  1500     // default pulse width when servo is attached

So I now fully understand what's going on, but am surprised that common servos have so much deviation from the standard 1000us to 2000us. (Which is obviously allowed for in the "Servo" library and 'write()'.) My servo isn't haunted after all.

All good information for the future. :) And thanks for the help, guys. Your replies prompted me to dig deeper.

And I'll bet that a lot of people have trouble with that adafruit "SoftServo" library. :D It just does this:-

void Adafruit_SoftServo::write(uint8_t a) {
  angle = a;

  if (! isAttached) return;
  micros = map(a, 0, 180, 1000, 2000);  
}

and has no 'writeMicroseconds()' function, so the library needs to be edited to get a full swing with most servos.