Servo library using the TCA timer(s) of modern 8-bit AVR Microchip processors. Depending on the processor, 3 or 6 servo's can be controlled by a single processor. Supported processors are MegacoreX, megaTinyCore (ATtiny 0, 1 and 2 series) and DxCore (DA, DB, DD, DU and EA series). As opposed to existing servo libraries, the PWM servo signal is generated by hardware, thus avoiding the jitter that may occur with existing libraries.
This library basically consists of two servo core libraries, plus two "high-level" libraries. These "high-level" libraries allow servo movement along a user defined curve. For some common curves, a couple of predefined curves have already been included. In addition, these "high-level" libraries allow the servo power and puls signals to be switch on or off.
There are two core libraries, one using the TCA0 timer, and another using the TCA1 timer. Since each TCA timer has three Compare Units / Waveform Outputs (WO), each library supports up to 3 servos. A sketch may include both libraries, thus the maximum number of servos is 6.
The TCA0 library requires the line #include <Servo_TCA0.h>
at the top of the user sketch, and makes available the class Servo
. This core library can be used for all processors mentioned above.
The TCA1 library requires the line #include <Servo_TCA1.h>
at the top of the user sketch, and makes available the class Servo1
. This core library can only be used with processors that have a TCA1 timer, thus the DA, DB and EA processors with 48 pins or more..
The core libraries are upwards compatible with the standard servo libraries, and implement all the methods that can be found in these standard servo libraries, as well as some additional methods. The (public) methods provided by the Servo
class are shown below; the Servo1
class provides exactly the same methods.
class Servo {
public:
uint8_t attach(uint8_t pin); // attach channel to a Compare Unit, sets pinMode, returns servoIndex or INVALID_SERVO
uint8_t attach(uint8_t pin, int min, int max); // as above but also sets min and max values (in us) for writes.
void detach();
void write(uint16_t value); // a value < MIN_PULSE_WIDTH is treated as an angle, otherwise as pulse width in microseconds
void writeMicroseconds(uint16_t value); // Write pulse width in microseconds
uint16_t readMicroseconds(); // returns current pulse width in microseconds
bool acceptsNewValue(); // New for the servo_TCA library: to avoid the delays(15), as seen in several examples.
void waitTillNextPulse(); // New for the servo_TCA library
void constantOutput(uint8_t on_off); // New for the servo_TCA library: sets output signal 5V (1) or 0V (0)
};
Compared to standard servo libraries, three new methods were added: acceptsNewValue()
, waitTillNextPulse()
and constantOutput(uint8_t on_off)
. These methods were added to allow better control regarding the start and stop behavior of the attached servo's.
Different servos behave differently when power is switched on. When power is switched on, many make abrupt short movements. To avoid such movements, see these instructions.
The core libraries allow servo's to move from one position to another. Such move is nearly instantaneous, which is often not desirable. A better approach would be that servos move slowly from one position to another or, even better, follow a specific curve between the start and end position. A number of curves have therefore been predefines, see this overview for details.
Since servos not only make noise while moving, but often also while in rest, wouldn't it be nice if we were able to switch the servo power and/or pulse signal off, once the end position is reached? This is exactly what the high-level libraries add to the core libraries.
The "high-level" libraries are strongly inspired by, and with respect to functionality, copied from the OpenDecoder 2 of the OpenDCC project.
Like the core libraries, there are two high-level libraries: one for TCA0 and the other for TCA1. Technically speaking, the first high-level library inherits the Servo class for TCA0, and the second inherits the Servo class from TCA1.
class ServoMoba: public Servo {
public:
enum idlePulseDefault_t { // default servo signal in the idle state
low,
high,
continuous};
void checkServo(); // Must be called as often as possible from the main loop
void moveServoAlongCurve(uint8_t direction); // Start moving along the path selected with initCurve
bool movementCompleted = true; // Flag to indicate servo is not moving
void initCurveFromEEPROM ( // use a predefined curve from EEPROM
uint8_t indexCurve, // 0..3
uint8_t timeStretch, // 1..255
int adresEeprom); // The starting address in EEPRROM of this curve
void initCurveFromPROGMEM( // use a predefined curve from PROGMEM
uint8_t indexCurve, // See curves.cpp for possible curves
uint8_t timeStretch); // 1..255
void initPulse( // What to do with the servo puls signal in idle state?
uint8_t idleOutput, // 0 is low (0V), everything else is high (3,3 or 5V)
uint8_t pulseBeforeMoving, // 0.255. Steps are in 20 ms
uint8_t pulseAfterMoving, // 0.255. Steps are in 20 ms
uint16_t initialPulseWidth); // Pulse width in ms for after startup
void initPower( // What to do with the servo power signal in idle state?
boolean idlePowerIsOff, // should power be switch off while idle?
uint8_t powerEnablePin, // the pin used to switch the servo power on and off
boolean powerEnableValue, // does the power enable hardware require a HIGH or LOW signal?
uint8_t powerOnBeforeMoving, // 0.255. Steps are in 20 ms
uint8_t powerOffAfterMoving); // 0.255. Steps are in 20 ms
void setTreshold1(uint16_t value); // Treshold1 can be higher or lower than Treshold2
void setTreshold2(uint16_t value); // Value in us.
uint16_t getTreshold1(); // returns Treshold1
uint16_t getTreshold2(); // returns Treshold2
uint16_t getFirstCurvePosition(); // returns the servo position for the start of the curve (in us)
uint16_t getLastCurvePosition(); // returns the servo position for the end of the curve (in us)
uint8_t previousCurve; // The curve that is currently loaded into myCurve
void powerOn(); // Switch power on
void powerOff(); // Switch power off
void printCurve(); // May be used for testing. Uses Serial1
}
Standard Arduino servo libraries rely on a single (usually 16 bit) timer to generate an interrupt (ISR) when the PWM puls for the current servo should end, and the puls for the subsequent servo should start. Within the ISR, functions like digitalWrite(), are generally used to switch the pulses on and off. This approach has as disadvantage that the exact time the pulses will change, may vary, depending on occurrence or absence of other interrupts. The standard servo PWM signal may therefore show some jitter, resulting into noise produced by the servo.
With this library, the PWM servo pulses are generated by hardware, using the Compare Unit / Waveform Output, that is available in many processors. Usage of such hardware results into a superior PWM servo signal.
The extras directory of this repository contains several pictures that compare the performance of this library to that of standard servo libraries. In addition, some slides are included that shows several details regarding the internals of this library.
The library has been tested on the following processors: ATMEGA 4809 (Arduino Nano Every), ATtiny 1607, ATtiny 3217, ATtiny 1627, AVR128DA48, AVR64DD32 and AVR64EA48. For 1 servo, it needs around 500 bytes of Flash and 10 bytes of RAM. For 3 servo's it needs around 800 bytes of Flash and 16 bytes of RAM. For 6 servo's 1600 bytes Flash and 32 bytes of RAM are needed.
The overhead of TCA interrupts is, when a single TCA timer is used (thus 1..3 Servo's), roughly 6us every 6,67 ms. When 2 TCA timers are used (upto 6 servo's), it is 6us per 3,33ms. For comparison: the overhead of the millis() timer is around 1,8us every 1ms.
See possible pins to learn which pins can be used on which processor. Use the provided examples to test this.