For my latest project I needed to connect an RC receiver to an Arduino and read the state of the PWM servo signals. I couldn’t find a library I liked that was interrupt-based, robust, and supported flexible output ranges and remapping. So I decided to build my own.
The Servo Input Library
#include <ServoInput.h> ServoInputPin<2> servo; void setup() {} void loop() { float angle = servo.getAngle(); // get angle of servo (0 - 180) }
The result is the Servo Input library, an open source solution for reading PWM servo inputs with an Arduino. This allows you to read servo positions from robotics, RC receivers, or any other devices with servo motors.
In comparison to the ‘typical’ method to read these signals, the built-in Arduino pulseIn()
function, this library allows servo inputs to be:
- Non-blocking (asynchronous)
- Range limited
- Flexibly remapped
- Buffered / noise resilient
Servo signals can be easily read in the background and remapped to a given output (USB, LEDs, motors, etc.) without any hand holding by the user. Add the library, connect the signal wires, and go.
Features
Non-Blocking
All ServoInputPin
objects make use of external interrupts to gather the PWM data from the servos. This means that the data collection happens in the background and is available instantaneously. No delays, no waiting.
It also means that you can execute other, blocking code with long delays while the servos positions are being read in the background. The servo data will be available whenever you need it.
Range Rescaling
Typical PWM servo signals have a 50 Hz update frequency (20 ms) and vary the motor angle with the duty cycle. The majority of motors will move to their center position with a 1500 µs pulse width and have a +/- 500 µs range (1000 – 2000 µs).
The library starts with these defaults but adds dynamic range rescaling. At any point the user can specify the minimum, maximum, or total range in microseconds. These range values are used whenever retrieving the pulse duration, so the reported angle or position will match the set angle of the motor.
What’s more is that these values are used for limiting the remapped output, so pulse durations that are outside of your servo’s physical range can be ‘clipped’ and ignored. This also helpfully restrains the output ranges when remapping to other values which prevents overflow.
Flexible Remapping
Speaking of, the library features flexible remapping functions to work the positional data into whatever form you need it.
In addition to the standard angle (degree) and percentage functions, the library also features a generic remapping function that will map the servo’s positional data between any two long
values. Great for controlling addressable LEDs (8 bit unsigned), a USB joystick (16-bit signed), or any other conceivable range.
Signal Buffering
If the signal is noisy then the data will get corrupted and none of these features matter. That’s why ServoInput includes signal validation and buffering.
When a new pulse has been detected and its value retrieved by the user, the pulse width is checked against a set range to see whether it’s valid. This position is then saved for future reference and used in the event that a noisy, invalid pulse is observed.
This keeps the input values stable and prevents hiccups from disrupting your project.
How It Works
The library’s core functionality relies on external interrupts and the standard Arduino micros()
function. For each ServoInputPin
object created by the user, the library creates a ‘change’ interrupt on the specified pin that triggers whenever the PWM signal pulses. On a change state, the interrupt triggers and the pin’s state is checked to determine the direction of the pulse (rising or falling). The interrupt routine then calls the micros()
function, based on AVR Timer0
, to calculate the pulse width of the servo signal in microseconds.
When a user calls one of the functions to read back the data (e.g. getAngle()
), this measured pulse width is buffered, compared against a range of valid widths, and then finally rescaled relative to the minimum and maximum pulse times provided by the user.
The underlying code takes advantage of a quirk of C++ templates, where each template instance (i.e. pin) is a wholly separate class type. This means that each static isr()
function and pulse data has unique, static memory storage even as part of an object. This allows up to 255 (uint8_t
) unique ServoInputPin
objects to be linked to the pin interrupt vectors with just a single function definition.
Behind the scenes, the ServoInputPin<>
class relies on the ServoInputSignal
base class for handling the range rescaling and data retrieval functions. This base also includes a linked list for iterative access, which is managed by the ServoInputManager
(available globally as ServoInput
) that provides convenience functions across all instances such as checking if all or any servo signals are available.
Download
The Servo Input library is licensed under the GNU Lesser General Public License and can be downloaded either from GitHub or through the library manager in the Arduino IDE.
For more information on how to use the Servo Input library, see the tutorial on how to use an RC controller with an Arduino.
If you use this in your own project, please let me know! I’d love to see what you create.
13 Comments
Andrew · October 1, 2020 at 4:12 pm
Dave – I haven’t tried it yet, but just read this and it seem perfect for what I need (working on a DIY custom flight control / HOTAS setup). . Just wanted to say thanks for putting in the effort and sharing!
Dave · October 1, 2020 at 6:21 pm
Glad you found it useful. Good luck on your project!
Pascal · October 29, 2020 at 10:05 am
Nice ! So simple to use, and work fine, I’m using it with an arduino Uno, but it seems it’s not compatible with arduino nano 33 BLE ?
Dave · October 29, 2020 at 11:48 am
Hi Pascal, thanks for trying the library. Currently the library uses a direct register access method which is incredibly fast and efficient but needs to be configured for each architecture. I don’t have a Nano 33 BLE so I didn’t modify the library to support it.
If you’d like to use the library with a Nano 33 BLE, please create a feature request issue on the GitHub page and I’ll see what I can do.
WADI RAHIM · December 29, 2020 at 5:08 pm
Dave, this is exactly like something I have been looking for. I loaded some examples and have run them to make sure I have the library installed successfully. Success.
I have been going through all the documentation you have pointed to. Is there a place where all the possible calls are laid out and defined? By going through the examples I am able to discern all the various calls and how they work. But I am sure to be missing out on many of the aspects of this rich library.
Dave · December 29, 2020 at 5:46 pm
Hi Wadi. The library is not commented for Doxygen, but you can read through the header file to see all of the available functions.
Yutiy · March 24, 2021 at 5:09 am
I have problem with Arduino Due.
“The ServoInput library does not support this board (platform). Please create a feature request here: https://github.com/dmadison/ServoInput/”
But on the page “https://www.partsnotincluded.com/how-to-use-an-rc-controller-with-an-arduino/” I see @Servo Signals and Interrupts … Due all digital pins”.
The Arduino Due is a microcontroller board based on the Atmel SAM3X8E ARM Cortex-M3 CPU.
But in ServoInput_Platforms.h I don’t found this platform.
Can I use ServoInput with Arduino DUE & how I can do it?
Dave · March 27, 2021 at 6:07 pm
Hi Yutiy. The Due’s architecture would have to be added to the `Platforms.h` file to be supported by the library. Please create an issue on the GitHub repository and I can look into adding support.
Terry Cornall · August 28, 2021 at 6:22 am
Thanks for the great work. I plan to use this in an RC tank that will form the platform for some experiments with location and mapping and machine vision.
Dave · August 28, 2021 at 8:19 am
Nice! I’m glad the library is getting some use. Best of luck with the experiments!
Stefan Schmitz · May 27, 2024 at 11:52 am
Hi Dave,
awesome work! I use it for a depth controller for my RC controlled Submarines. Without your Library this wouldnt be possible for me.
I use it with a Leonardo aka Pro Micro from Aliexpress.
Is it possible, to detect the absence of a valid receiver signal within a given resolution?
Keep up the good work!
Cheers
Stefan
Dave · May 28, 2024 at 9:59 am
Hi Stefan! I’m glad you found the library helpful, RC subs are awesome!
There was an issue opened awhile back where someone was interested in a ‘timeout’ feature that would report whether there was no valid signal present, but they disappeared before the feature was merged. If that sounds useful to you, please create an issue on the library repo and I can look into it again.
Stefan Schmitz · May 29, 2024 at 5:09 pm
Hi Dave,
thanks for the reply. I left an issue at github and watch out for changes.
Thanks and keep up the good work!
Regards
Stefan