Learn about the de facto standard for controlling servos, PWM!
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
SineWavePWM
images
README.md

README.md

PWM Introduction

PWM, or Pulse Width Modulation, is the de facto standard for controlling servos and electronic speed controllers (ESC). In this introductory tutorial we will explore what PWM is and then practice what we've learned by commanding some servos to move! This tutorial will use a Teensy 3.2 along with the Teensy PWM Backpack.

Necessary Hardware

  • Teensy 3.2
  • Teensy PWM Backpack
  • Servos, analog or digital will work
  • Voltage regulator to power the servos. Typically servos are powered with 5V DC; however, they can draw a lot of current! I recommend using either: a 5V DC power supply capable of providing at least 2A of current (i.e. a commonly found wall wart power supply may work) or an ESC with a battery eliminator circuit (BEC).

Pulse Width Modulation

Like the name implies, pulse width modulation involves sending each servo a series of pulses where the width of the pulse determines the position the servo is commanded to travel to. PWM signals are typically generated by either an RC receiver or, as in this case, a microcontroller like the Teensy 3.2. PWM servos commonly expect a pulse between 1 ms and 2 ms in duration and a new pulse every 20 ms, or 50 Hz. This means that typically a pulse 1 ms in duration commands the minimum position, a 1.5 ms pulse the middle position, and a 2 ms pulse the maximum position.

ServoPWM

These pulse widths and update rates are holdovers from the early 1990's when they provided a simple means of converting the radio modulation used in the RC transmitter and receiver to servo commands. The widths and update rates still work well for the majority of applications; however, there are a few applications requiring higher frequency responses - typically multi-rotor ESCs and single rotor (AKA helicopter) tail rotor servos. In these cases, the servo update rates are typically around 333 Hz (although a few are even faster) with a 0.5 ms to 1.5 ms range.

The beauty of PWM is that the PWM signal does not directly drive the servo output shaft, rather it is simply sending a position command to the servo. Inside each servo is a controller that commands the motor and servo output shaft to the commanded position. Due to this approach, the PWM signal is relatively low current, allowing our microcontroller to command it directly.

Wiring

For this tutorial, we will be using a Teensy 3.2 and the Teensy PWM Backpack.

First, plug your Teensy 3.2 into the Teensy PWM Backpack with the USB connector on the end of the backpack that has a white dot; this dot marks the location of the Teensy ground pin.

PWM servos have 3 wires: a signal wire carrying the PWM signal, a power wire supplying power for the servo motor to move, and a ground wire. Typically, the PWM signal wire is white, orange, or yellow. Ground is typically black or brown. Power is always red. Plug your servos into the PWM backpack with the signal wire up. The power wire should be in the middle with the ground at the bottom.

closeup

The Teensy PWM Backpack buses all of the servo power and grounds together, so simply connect your voltage regulator power and ground to an unused channel. For this tutorial, we will not be using the SBUS RX capability of the Teensy PWM Backpack, so either make sure the solderpad is connected to VDD instead of 5V or do not connect anything to Channel 8.

setup

Software

There is an Arduino servo library which lets you send PWM commands, but I recommend using the Teensy PWM library.

The Teensy PWM library is Arduino compatible and the reasons I recommend it are:

  1. Enables you to easily change the update rate, which is useful for when your project expands to use higher bandwidth servos or ESCs.
  2. The Teensy PWM library sends all of the PWM commands simultaneously whereas the Arduino library sends them round robin. In other words, the Arduino servo library will send the pulse to one servo, and then the next, and so on. With the Teensy PWM library, the start of the pulse occurs simultaneously for all servos. Although beyond the scope of this tutorial, this is desireable behaviour, especially when considering stability and robustness of automated control systems.

Let's get started!

Installation

The Teensy PWM library is included when Teensyduino is installed. For this tutorial, you can clone or download the code in this repository and follow along or code it yourself.

Tutorial

Goals

The goal of this tutorial is to create a periodic function to command our servos to move across their entire range. We will use this goal to learn about PWM and using it with the Teensy 3.2 and Teensy PWM Backpack.

Code Walkthrough

Constants

First, we're going to code some useful constants and make them available globally by placing them outside of the setup and loop functions at the start of the file.

It's easier for me to refer to the PWM channel numbers marked on the Teensy PWM Backpack rather than the Teensy pin numbers they correspond to. Using the Backpack pinout, we'll create an array of pin numbers with each index of the array corresponding to the Teensy PWM Backpack channel number.

const unsigned int PWM[8] = {20,21,22,23,5,6,10,9};

Next, well set the PWM update rate as 50 Hz and store that information in a variable.

const unsigned int Freq = 50;

The reason we did this is so that we could compute the period, which will be needed later. In this case, we're computing period in terms of microseconds or us.

const unsigned int Period_us = 1000000 / Freq;

We'll set a variable for the PWM resolution as well, which will be needed later. In this case we'll use the full 16 bit capability.

const unsigned int PWM_Resolution = 16;

One of the easiest ways to create a periodic command is by generating a sine wave and for this we'll need a good source of time. In this case, we're using a millisecond accuracy timer and elapsedMillis for convenience; although, using millis would have worked as well.

elapsedMillis time_ms;

Setup

We'll print the command to the Serial Monitor so that we can see how it's changing, so we better start the USB Serial communications.

Serial.begin(115200);
while (!Serial && time_ms < 5000) {}

Now, we'll set the update rate and resolution for each PWM channel.

for (unsigned int i = 0; i < sizeof(PWM) / sizeof(unsigned int); ++i) {
  analogWriteFrequency(PWM[i], Freq);
  analogWriteResolution(PWM_Resolution);
}

Loop

The first item of business is to compute the sine wave. In this case we're using a 1 Hz sine wave.

float Cmd = sinf(2.0f * M_PI * time_ms / 1000.0f);

The sine wave will give us a result in the range of -1 to +1. Our servos expect a range of 1 ms to 2 ms or 1000 us to 2000 us. So we'll convert our +/- 1 command to a 1000 to 2000 us command using a scale factor and a bias.

Cmd = Cmd * 500.0f + 1500.0f;

Finally, we'll step through each channel and issue the command. Notice, however, that the Teensy PWM library is expecting a duty cycle input, in addition to the pin number. To compute duty cycle we divide the pulse duration by the period. Additionally, the Teensy PWM library is expecting this given in terms of the PWM resolution. For the 16 bit case, a value of 65535 would correspond to 100% duty cycle. So, after we compute our duty cycle we need to scale it by the resolution.

for (unsigned int i = 0; i < sizeof(PWM) / sizeof(unsigned int); ++i) {
  analogWrite(PWM[i],Cmd / Period_us * powf(2,PWM_Resolution));
}

Now we'll print the command to the Serial Monitor and add a delay so that it's easier to read the commands.

Serial.println(Cmd);
delay(20);

Done! Congrats, your code should compile and be ready to upload to the Teensy 3.2.

Experiment

Upload your code to the Teensy and apply servo power. You should see your servos move through their full range of motion repeating every second. Try to compare the servo arm position with the command being printed to the Serial Monitor. Most servos will move through almost 180 degrees of motion. Notice how 1000 us corresponds to the minimum position, 2000 us to the maximum position, and 1500 us is in the middle?

You can find a video of the results here:

Experiment Video

Wrap Up

In this tutorial we learned about Pulse Width Modulation, or PWM, which is commonly used to send position commands to servos. We used a Teensy 3.2 and the Teensy PWM Backpack to command servos to move to positions computed using a sine wave.

Further Reading

  1. Wikipedia: quick background information on PWM.
  2. SparkFun: in depth information and pictures of the insides of servos.
  3. Princeton: very in depth information about the PWM signals and servo circuits.

Next Steps

Armed with the knowledge and experience that you gained with this tutorial, see if you can do the following:

  1. Change the sine wave frequency. How would you make it faster? Slower?
  2. Command different periodic functions. Can you make the servos step to a position at a given time? Step back?
  3. Write a program that allows you to enter a PWM command in the Serial Monitor and command the servos to move to that position.
  4. Try tracing the full range of the servo arm on a piece of paper and estimate its angle. Send the servo a PWM command, mark the resulting position and estimate its angle. Do this for several different PWM commands throughout the full range. Can you estimate a function to convert angles to PWM commands? Try writing a function that will take an angle command as a parameter, convert the angle to an appropriate PWM command, and send that command to the servo.

Tutorials in the Series

  1. PWM Introduction: Learn about the de facto standard for controlling servos, PWM!
  2. Reading PWM: Gain experience measuring pulse widths!
  3. SBUS Introduction: Learn about one of the newest, coolest methods of commanding servos, SBUS!
  4. Reading SBUS: Gain experience reading SBUS packets!
  5. SBUS to PWM Converter: Use your knowledge of PWM and SBUS to create your own SBUS to PWM converter!