Interfacing with Servomotors

By: Paul Bartosek

Overview

Every year the projects done in EE 552 get more complex and more interesting. Among the flashier projects are ones that move. This can be done using standard DC or stepper motors if you don't need to move something an exact amount. But if need to move or turn an exact amount repeatedly you need a servomotor. You've all seen them before and you know what they do, they are generally used in model cars/boats/planes etc. but are great for use with digital hardware. They are neat little motors that turn a full 90 degrees and provide loads of torque, they are available at your local hobby shop for a tidy sum and are worth every penny.


Interfacing

There are three leads coming from the servo. Red and black are fairly obvious, power and ground respectively. Generally these model servos expect a 6 volt supply voltage, but they are more than happy to run on 5 volts, like from the regulator on your UP1 board. Now the other wire (usually white or yellow) is the control signal that runs from your controller into the servo, telling it what position you want it in. A pulse width modulated signal with a 60 Hz frequency is required to operate the servomotor correctly, there is a built in decoder that converts the pulse width into a position for it. The width of the pulse should range between 1 ms and 2 ms this will move the servo actuator linearly through the range of the servo (ie. 1 ms = 0 degrees, 1.5 ms = 45 degrees, 2 ms = 90 degrees, etc.) This pulse must be continuously applied since it does take a significant, relative to the system clock speed amount of time for the servo to rotate.

A note on noise: Do not be fooled by their size, these servos contain quite powerful DC motors and as such create a large amount of electromagnetic noise. Ordinarily one would not operate sensitive analog circuitry in close proximity to them, nor power said circuitry from the same source as the servos are powered from.


VHDL Code

Shown below is an example of a VHDL function which takes in a 4-bit vector that represents the position you want the servo to rotate to. Sixteen different positions should be enough for most applications but this code is fully scalable to allow for a higher resolution. Currently with compiler optimizations enable this occupies a paltry 36 logic cells on the FLEX10K20.

Or if you would like to download it click here.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

entity servo is
   generic (
      -- controls clock divider frequency
      divisor      : positive := 771;
      -- adjusts center position of the servo
      trim         : positive := 18;
      -- servo_period controls the output peroid
      -- and should be adjusted if "divisor" is changed
      servo_period : positive := 272
   );
   port (
      clk, reset  : in std_logic;
      servo_pos   : in std_logic_vector(3 downto 0);
      servo_out   : buffer std_logic
   );
end servo;

architecture behavioral of servo is
signal slow_clk : std_logic;
signal slow_clk_count : std_logic_vector(10 downto 0);
signal servo_counter : std_logic_vector(8 downto 0);

begin

  -- a simple clock divider
  -- divides by (divisor x 2)
  process (clk)
  begin
    if reset = '0' then
      slow_clk_count <= (others => '0');
      slow_clk <= '0';
    elsif clk'event and clk = '1' then
      if slow_clk_count >= divisor then
        slow_clk_count <= (others => '0');
        slow_clk <= not slow_clk;
      else
        slow_clk_count <= slow_clk_count + 1;
      end if;
    end if;
  end process;

  -- this process controls the width of the pulse
  -- being sent to the servo,
  -- proportional to servo_pos
  process (slow_clk)
  begin
    if reset = '0' then
      servo_counter <= (others => '0');
    elsif slow_clk'event and slow_clk = '1' then

      -- servo_period controls the period
      -- of the signal which is sent to
      -- the servo
      if servo_counter >= servo_period then
        servo_counter <= (others => '0');
      else
        servo_counter <= servo_counter + 1;
      end if;

      -- depending how far into the period
      -- we are output a '1' or a '0'
      if servo_counter < (servo_pos + trim) then
        servo_out <= '1';
      else
        servo_out <= '0';
      end if;
    end if;
  end process;
end behavioral;

Questions? Comments? Concerns?
Email bartosek@ee.ualberta.ca