Arduino control servo without library: Find out an easy way to do it here; the key is combining a non-blocking delay with a precise microsecond timer!

"Arduino control servo without library" can it be done? The short answer is yes!..but...it's not the ideal way to do it. You definitely won't get as many working servos compared to library code, but using this method does work.

You are probably here because all your timers are doing something else and can not be used to drive a servo. Note: Servos use Timer1 but there is a library that uses Timer2.

If can you should really consider using the built-in servo library that comes with the Arduino code and is very good at driving 12 servos - all interrupt driven and working in the background.

The key to doing it without a library is to understand how a servo motor is driven. i.e. what exact signal is required to operate a servo.

You can see that a servo only has three wires and two of them provide power and ground. In fact the third is a serial signal that is actually a PWM signal.

What is the Servo control signal?:Arduino control servo without library

The servo motor is controlled by sending it a pulse-width modulated (PWM) signal on its control line. The signal consists of a series of electrical pulses that vary in width to convey positional information to the servo.

Specifically, the PWM signal is a train of rectangular voltage pulses. The pulse duration, or width, determines the servo's angular position. Typically each pulse is 20 milliseconds long, and the interval between pulses is also 20 ms, giving a pulse repetition rate of 50 Hz.


s3003 pwm and output positions luxorparts datasheet
Image Source: luxorparts datasheet.

Within each 20 ms pulse, there is a high-voltage section between 1-2 ms wide, representing the commanded position. A 1.5 ms pulse would tell the servo to go to its center position. The for S3003 servo above, longer pulses rotate the servo shaft anti-clockwise from center, shorter pulses rotate it clockwise.


The high-voltage section of the pulse is typically 5 volts. The remainder of the 20 ms pulse is 0V.

This results in a 5V square wave with a precision-controlled ON duration that is modulated between 1-2 ms to encode position data. The rest of each 20 ms period is OFF at 0V.

The servo has circuitry to decode this PWM width into a proportional voltage that powers its DC motor. As the pulse width changes, the voltage applied to the motor changes, causing it to rotate its shaft to the new angular position demanded by the master control circuit. With milliseconds-precision pulses, the servo can achieve very fine rotational resolution.

So in summary, the technical details of the servo control signal are a 50 Hz PWM voltage waveform varying in ON pulse width from 1-2 ms out of a 20 ms total period, with 5V high and 0V low levels. This conveys sub-millisecond positional commands to the servo motor.

Important servo signals: Arduino control servo without library


The most important point in creating and using an Arduino control servo without library is that the high part of the signal must be accurate.

On the other hand the low period (or repeat rate) is not as significant. this does however, depend on the servo i.e. how far you can increase the repeat period time before the servo stops working correctly is entirely dependent on the servo you are using.

TIP: Try increasing the repetition period for your servo this allows more processor time but it depends on the servo circuit. For an SG90s it allows 100ms (not under load) -seemingly with no bad effect! but slowed an S3003. However check the holding force.

Using digitalWrite to control a servo

The simplest case:


You can make a servo driver using two delay functions delay() and delayMicroseconds(). This is the type of code you could use:

#define SERVO_PIN 9 #define PULSE_HIGH_TIME 1500 // Adjust for your specific servo #define REFRESH_RATE 20 // Set the output refresh rate in milliseconds void setup() { pinMode(SERVO_PIN, OUTPUT); } void loop() { digitalWrite(SERVO_PIN, HIGH); delayMicroseconds(PULSE_HIGH_TIME);

  digitalWrite(SERVO_PIN, LOW); delay(REFRESH_RATE - (PULSE_HIGH_TIME+500)/1000); }


The problem with this code is that it uses blocking delays so your processor can do nothing else except drive the servo. For this code the delayMicroseconds() function provides an accurate high pulse while the less accurate delay function produces a delay to the nearest millisecond.

This is not ideal so...

...see the next section:

A more interesting case:


This technique uses a combination of the non-blocking millis() timer delay, and the blocking-delay delayMicroseconds() function.

The millis() timer is used to provide an approximate 20ms repeat rate while the microsecond delay function is use to provide the accurate pulse high signal. During the pulse high output the processor can do no other work. This is the sacrifice your code makes to allow accurate servo positioning.

The advantage of this method is that you also use the non blocking delay timer millis(). So for the rest of the time the processor is free to do other stuff - just not during the time that the signal is high.

Also, you have to ensure that the processor activity is done before the low period of the servo output so the max time is ~20ms-2.5ms = 17.5ms. Here 2.5ms is the maximum as this is what an SG90 and S3003 needed for 180 Degree rotation.

TIP: Increasing the repeat time (REPEAT_RATE_MS) allows more processor time but it depends on the servo circuit. For an SG90s (not under load) it allows 100ms -seemingly with no bad effect! but slowed an S3003. However check the holding force.

Single servo Sketch: Arduino control servo without library

// Single Servo : Arduino control servo without library
// Copyright John Main: TronicsBench.com // Free for use in non- commercial projects.
#define servoPin 9 #define minPulseWidth 500 #define maxPulseWidth 2500 #define REPEAT_RATE_MS 20 unsigned long angleChangeTime = millis(); unsigned long pt20ms = millis(); int angle = 0; void setup() { // Serial.begin(115200); pinMode(servoPin, OUTPUT); } void loop() { static int8_t angle_dir = 1; static int pulseTime=500; unsigned long currentTime = millis(); if(currentTime - pt20ms >= REPEAT_RATE_MS) { digitalWrite(servoPin, HIGH); delayMicroseconds(pulseTime); digitalWrite(servoPin, LOW); pt20ms = currentTime; } // Move to next position after x seconds if(millis() - angleChangeTime >= 1000) { // Serial.println(angle); // Serial.println(pulseTime); angle+=90*angle_dir; if (angle >= 180) { angle_dir =-1; } if (angle <= 0) { angle_dir = 1; } pulseTime = map(angle, 0, 180, minPulseWidth, maxPulseWidth); angleChangeTime = currentTime; }

servo-no-lib.ino

What else can you do with this code?


One thing you may want to do is to drive another servo. The way you would do that is to add another servoPin and delayMicroseconds() section in the main loop.

This does however reduce the processing time by up to another 2.5ms.

Two servo Sketch: Arduino control servo without library


// Two Servos : Arduino control servo without library
// Copyright John Main: TronicsBench.com
// Free for use in non- commercial projects.
#define servoPin 9 #define servoPin2 10 #define minPulseWidth 500 #define maxPulseWidth 2500 #define REPEAT_RATE_MS 20 unsigned long angleChangeTime = millis(); unsigned long pt20ms = millis(); int angle = 0; void setup() { // Serial.begin(115200); pinMode(servoPin, OUTPUT); pinMode(servoPin2, OUTPUT); } void loop() { static int8_t angle_dir = 1; static int pulseTime=500; static int pulseTime2=500; unsigned long currentTime = millis(); if(currentTime - pt20ms >= REPEAT_RATE_MS) { digitalWrite(servoPin, HIGH); delayMicroseconds(pulseTime); digitalWrite(servoPin, LOW); digitalWrite(servoPin2, HIGH); // 2nd servo delayMicroseconds(pulseTime2); digitalWrite(servoPin2, LOW); pt20ms = currentTime; } // Move to next position after x seconds if(millis() - angleChangeTime >= 1000) { // Serial.println(angle); // Serial.println(pulseTime); angle+=90*angle_dir; if (angle >= 180) { angle_dir =-1; } if (angle <= 0) { angle_dir = 1; } pulseTime = map(angle, 0, 180, minPulseWidth, maxPulseWidth); pulseTime2 = map(angle/2, 0, 180, minPulseWidth, maxPulseWidth); // half the movement of the other. angleChangeTime = currentTime; } }

servo-no-lib-2servos.ino


Written by John Main who has a degree in Electronic Engineering.

Note: Parts of this page were written using claude-instant as a research assistant.




Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.



Privacy Policy | Contact | About Me

Site Map | Terms of Use