Controlling an RC Servo with an FPGA

This time, we will look at how to control a regular RC hobby servo. They come in many different sizes and gearings, but for the basic control, they all use a simple PWM (Pulse Width Modulation) signal, to indicate the desired position.

The width of the pulse should be between 1000 and 2000µs, with 1500µs representing the center position. Some brands have slight differences as to where the center point is, and some also allow pulses to be smaller than 1000µs and larger than 2000µs, but you can’t count on that. You also need to send a pulse to the servo at least every 20ms, otherwise the servo will likely enter standby, and it will no longer hold it’s position if there is a load on the arm.

The implementation below is loosely based on the design described at fpga4fun.com, but instead of Verilog, I have decided to use VHDL.

In addition to controlling the servo, I have tried to separate the project into multiple entities, as a preparation to expand with other controls.

Servo Driver
First we have the VHDL code to implement the servo driver. It has an input for the clock signal and an 8bit vector for the position. On the output side, it has a single output for the servo control line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
entity ServoDriver is
    Port ( Clk : in  STD_LOGIC;
	        Position : in STD_LOGIC_VECTOR (7 downto 0);
           Servo : out  STD_LOGIC);
end ServoDriver;
 
architecture Behavioral of ServoDriver is
 
constant ClockDiv: integer := 63;
 
signal ClockTick: std_logic := '0';
signal ClockCount: std_logic_vector (6 downto 0) := "0000000";
 
signal PulseCount: std_logic_vector (11 downto 0) := "000000000000";
 
begin
 
process (Clk)
begin
	if Clk='1' and Clk'event then
		if ClockCount = ClockDiv-2 then
			ClockTick <= '1';
		else
			ClockTick <= '0';
		end if;
		if ClockTick='1' then
			ClockCount <= "0000000";
		else
			ClockCount <= ClockCount + 1;
		end if;
	end if;
end process;
 
process (Clk)
begin
	if Clk='1' and Clk'event then
		if ClockTick='1' then
			PulseCount <= PulseCount + 1;
		end if;
		if PulseCount < ("0001" & Position) then
			Servo <= '1';
		else
			Servo <= '0';
		end if;
	end if;
end process;
 
end Behavioral;

Button controller
To test out the ServoDriver above, I made a little entity, that uses button presses to increment and decrement a counter. It’s fairly simple, the most tricky part is the conversions, to make sure it’s running unsigned all the time, especially when the 8bit ServoPos signal is wrapping around.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
entity Controller is
    Port ( ButtonDown : in  STD_LOGIC;
           ButtonUp : in  STD_LOGIC;
           Position : out  STD_LOGIC_VECTOR (7 downto 0));
end Controller;
 
architecture Behavioral of Controller is
 
signal ServoPos: unsigned(7 downto 0) := "00000000";
 
signal Button: std_logic;
signal Direction: std_logic;
 
begin
 
process (Button)
begin
	if Button='1' and Button'event then
		if Direction='1' then
			ServoPos <= ServoPos + 4;
			Position <= conv_std_logic_vector(ServoPos, 8);
		else
			ServoPos <= ServoPos - 4;
			Position <= conv_std_logic_vector(ServoPos, 8);
		end if;
	end if;
end process;
 
Button <= ButtonDown xor ButtonUp;
Direction <= ButtonUp and not ButtonDown;
 
end Behavioral;

Top module
Finally, I made an entity to connect the Controller with the ServoDriver. It makes two instances, one of each entity, and has an 8bit signal (Position) between the two.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
 
entity ServoUpDown is
    Port ( Clk : in  STD_LOGIC;
           ButtonUp : in  STD_LOGIC;
           ButtonDown : in  STD_LOGIC;
           Servo : out  STD_LOGIC);
end ServoUpDown;
 
architecture Behavioral of ServoUpDown is
 
signal Position : std_logic_vector (7 downto 0);
 
begin
 
Servo1: entity ServoDriver port map (Clk, Position, Servo);
Controller1: entity Controller port map (ButtonDown, ButtonUp, Position);
 
end Behavioral;

UCF file for the AVNET Xilinx® Spartan®-3A Evaluation Kit
I’m using the following UCF file to connect the above with the outside world. The servo has the negative lead connected to GND on the board, positive to +5v, and the signal wire connected to D10. The two buttons are PUSH_A and PUSH_B on the board, connected via the PSoC.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#Created by Constraints Editor (xc3s400a-ft256-4) - 2010/06/17
NET "Clk" TNM_NET = "Clk";
TIMESPEC TS_Clk = PERIOD "Clk" 62.5 ns HIGH 50 %;
 
# PlanAhead Generated physical constraints 
NET "Clk" LOC = C10;
NET "ButtonUp" LOC = K3;
NET "ButtonDown" LOC = H5;
NET "Servo" LOC = D10;
 
# PlanAhead Generated IO constraints 
NET "Clk" IOSTANDARD = LVCMOS33;
NET "ButtonUp" IOSTANDARD = LVCMOS33;
NET "ButtonDown" IOSTANDARD = LVCMOS33;
NET "Servo" IOSTANDARD = LVCMOS33;

The servo should ideally use 5v logic for the signal, but many servos will accept a 3.3v signal without any problems including the Hyperion Atlas Digital servo I’m using (HP-DS13-ACB).

Below is a short video showing the servo move as the buttons are pressed.

33 thoughts on “Controlling an RC Servo with an FPGA”

  1. Kyle says:

    Hey there,
    Great vhdl code I have used these ideas to control a servo for a project. I have a question to ask though, I was wondering how the std_logic_vector works for the clock divider…I am puzzled as to how a count up to 64 (in your first code) can divide a clock all the way down to 50Hz? Thanks in advance.

  2. Hi Kyle,

    It’s not going all the way down to ~50Hz with that counter, it only divides down to about 250kHz, which is the smalles variation in pulse width possible, and then uses the overflow on PulseCount to start the next pulse, which ends up a little more often than 50Hz (closer to 62Hz), but most, if not all servos can accept pulses much quicker often up to almost 400Hz.

    /Thomas

  3. Tahir Sarı says:

    hello thomas.i have a project like yours.According to my project i have to control servo motors angle and speed.I think i can write a code like yours for speed but do you have an idea for angle control code??

  4. The angle of the servo is controlled by the width of the pulse or in the VHDL code above the value of the Position signal in the top module.

    To control the speed, you need to limit the rate of how fast you change that value towards you desired position/angle.

    I hope the above helps!

  5. Kalmah says:

    hi, thanks for providing a very clear tutorial on controlling servos with fpgas. i’m a newbie to fpgas. i want to implement this exact vhdl code in my spartan-3e starter kit which contains a 50 mhz clock onboard. for this i used the following formula:
    50000000 / (1000 * 256) = 195.3125 ~ 196

    so, ServoDriver line#15 was replaced as:
    constant ClockDiv: integer := 196;

    my .UCF file is as follows (adopted from the default spartan-3e starter .ucf file)
    ——————————-
    # Period constraint for 50MHz operation
    #
    NET “clk” PERIOD = 20.0ns HIGH 50%;
    #
    # soldered 50MHz Clock.
    #
    NET “clk” LOC = “C9″ | IOSTANDARD = LVTTL;
    #NET “ButtonUp” LOC = “V4″ | IOSTANDARD = LVTTL | PULLDOWN;
    #NET “ButtonDown” LOC = “K17″ | IOSTANDARD = LVTTL | PULLDOWN;
    NET “RCServo_pulse” LOC = D10 | IOSTANDARD = LVTTL | PULLDOWN;

    —————————–
    all other code is EXACTLY same, however, i couldn’t complete the “Synthesis” step and the “implement design”. i’m using Xilinx ISE 8.2i… can you please help me where i went wrong. :(

    =========================================================================
    * Advanced HDL Synthesis *
    =========================================================================

    Loading device for application Rf_Device from file ‘3s500e.nph’ in environment C:\Xilinx.
    WARNING:Xst:1426 – The value init of the FF/Latch Servo hinder the constant cleaning in the block ServoDriver.
    You should achieve better results by setting this init to 1.
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .

    =========================================================================
    =========================================================================
    * Low Level Synthesis *
    =========================================================================
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .
    WARNING:Xst:1291 – FF/Latch is unconnected in block .

  6. Hi Kalmah.
    I just saw this post and your comment, and thought that I should be answering your question.
    I’m and electronics entrepreneur with speciality in embedded development (microcontrollers and FPGA’s), so I have a lot of experience in this field.
    If you have followed this tutorial as written, and used the same code, you shouldn’t have any problems. I noticed that you are using Xilinx ISE 8.2i though, which is a very old version – so please try to download the 12.4 (wait with the ISE 13.1)
    You can download it here: http://www.xilinx.com/support/download/index.htm

    I think updating to this version will fix the problem!

    Best Regards
    Thomas Jespersen
    TKJ Electronics – http://www.tkjelectronics.dk

  7. Kalmah says:

    @thomas: i had a feeling that the old version of the software would be a problem but thought it would still work. my download speed here is just 192kpbs (nepal) hehe…. arrh the time it’ll take to download the new ISE! :) i shall test it out as soon as the download is complete. thanks for helping me out with the suggestion!

  8. Shakeel says:

    Hello Thomas,
    Thanks for the VHDL code. i just want to know about the connection between Rc Servo and FPGA kit. i m using SPARTAN 3E.

  9. Hi Shakeel,

    I’m just feeding the servo +5V and GND from the power rail on my board, and then connecting the signal pin to the pin that I configured in the UCF file (I used pin D10 since it was easy to connect to on my dev board).

    To be fully on spec, you should do level shifting from 3.3V to 5V, but for testing it will often work with just 3.3V for the control signal, at least for digital servos.

    /Thomas

  10. Ahmed says:

    Sir. Thomas,
    I am having problem while using this code in automated form, my fsm provides inputs following in puts first 00000000 for 10s, then 00000011 for 10s to come in middle position and then back to its initial position i.e 00000000, But when i run the program the servo moves to extreme right and the no reply. Can you please help me out in mt automated design.

  11. pablo says:

    Hello,
    I was wondering why did you choose 63 as your clock division and clock count?

  12. Hi Pablo,

    The number 63 comes out of some calculations that are based on the Clk defined in the UCF file and the smallest unit of time that we need to vary the servo pulse with.

    We have a resolution of 256 steps spread across 1 ms, and I’m using a 16 MHz Clk signal, so the math is: 16000000 / 1000 / 256 = 62.5 ~ 63 as divider.

    There is a bit more detailed explanation of this at http://www.fpga4fun.com/RCServos.html, where I have gotten the inspiration to this little experiment.

    /Thomas

  13. Hi Ahmed,

    If I understand it correctly, I think it’s a problem with the bit ordering (11000000 vs. 00000011). I must admit that it’s been awhile since I have worked with this, but I think the MSB (most significant bit) is the first one (furthest to the left), which means that 00000000 and 00000011 are two values very close to each other and only changing about 1%, which on a normal hobby servo is barely visible.

    I might be mistaking, but this it where I would be checking first.

    Hope you get it working,

    /Thomas

  14. Alex says:

    hello,
    I tryed using your code but the servo dosen’t even budge…it just sits there…the servo is a GWS S03N STD. I feed it current from a 5v charger (socket), tryed directly from board and nadda. I tryed with different servo..still nothing. My board is SpartanE3-250 Basys2.

  15. Alex says:

    Sorry for double post butI get a load of warnings, I cant make heads or tales of them

    WARNING:Xst:1290 – Hierarchical block is unconnected in block .
    It will be removed from the design.
    WARNING:Xst:1710 – FF/Latch (without init value) has a constant value of 1 in block . This FF/Latch will be trimmed during the optimization process.
    WARNING:Xst:1710 – FF/Latch (without init value) has a constant value of 1 in block . This FF/Latch will be trimmed during the optimization process.
    WARNING:Xst:1293 – FF/Latch has a constant value of 0 in block . This FF/Latch will be trimmed during the optimization process.
    WARNING:Xst:1293 – FF/Latch has a constant value of 0 in block . This FF/Latch will be trimmed during the optimization process.
    WARNING:Xst:1895 – Due to other FF/Latch trimming, FF/Latch (without init value) has a constant value of 0 in block . This FF/Latch will be trimmed during the optimization process.
    WARNING:Xst:1895 – Due to other FF/Latch trimming, FF/Latch (without init value) has a constant value of 0 in block . This FF/Latch will be trimmed during the optimization process.
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:Xst:2677 – Node of sequential type is unconnected in block .
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The signal
    does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.
    WARNING:Par:288 – The signal ButtonU1_IBUF has no load. PAR will not attempt to route this signal.
    WARNING:Par:288 – The signal Clk_IBUF has no load. PAR will not attempt to route this signal.
    WARNING:Par:288 – The signal ButtonD1_IBUF has no load. PAR will not attempt to route this signal.
    WARNING:Par:283 – There are 3 loadless signals in this design. This design will cause Bitgen to issue DRC warnings.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The signal
    does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.

  16. Hi Alex,

    I’m not 100% sure it’s the only problem, but are you sure you have connected the signals to the physical pins on your board. Since you are using a different board, you will likely need to use different pins in the UCF file.

    If the signals aren’t connected to anything, I’m pretty sure all the logic is optimized away, and nothing will happen.

    Also… are you using the VHDL as is, or have you changed it to fit with something else? The first warning you get indicates that something is missing.

    Hope it helps!

    /Thomas

  17. Alex says:

    ty for the replay.
    I’ve recreated the UCF and the warnings have changed. Implemented it and still nothing.The only thing I cahnged is the clk which in my case is 50Mhz so 196 divider.
    I will check back whenever I can. A thing comes to mind, what do your buttons send when pressed? 1 or 0? (seen fpgas with 0 on press and 1 on release)

    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The signal
    does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.
    WARNING:Par:288 – The signal ButtonU1_IBUF has no load. PAR will not attempt to route this signal.
    WARNING:Par:288 – The signal Clk_IBUF has no load. PAR will not attempt to route this signal.
    WARNING:Par:288 – The signal ButtonD1_IBUF has no load. PAR will not attempt to route this signal.
    WARNING:Par:283 – There are 3 loadless signals in this design. This design will cause Bitgen to issue DRC warnings.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The signal
    does not drive any load pins in the design.
    WARNING:PhysDesignRules:367 – The signal is incomplete. The
    signal does not drive any load pins in the design.

  18. The buttons on my board are not normal buttons, but capacitive touch, controlled by a little PSoC, and then fed into the FPGA as normal logic, but it shouldn’t matter, you should still get some function even if the logic levels are inverted.

    All the warnings that are left seems to be related to signals not connected in some way. I don’t remember having seen those warnings before, but I would guess it’s a name mismatch or something like that.

    I’m traveling at the moment, so I can’t even try to reproduce until in about two weeks when I get back.

    Feel free to post again, and I’ll do my best to help!

    /Thomas

  19. Alex says:

    Hello again. I’ve managed to get it going and works like charm. The problem is I use 5 servos. Powering 3 of them off the fpga is gold, but when connecting the 4th to fpga power rail everything starts churning. The servos go bzerk. Can you give me another way to power them?…I tryed directly from usb and it dosent work (I think it also shorts the USB, ty god for the recovery mechanism). Best regards

  20. When doing more than just testing with one or two servos unloaded, you should have a separate power rail for the servos, since they can pull a lot of current (some of the standard size hobby servos have a stall current of about 2A each).

    Simply use a common ground (GND) and feed the signal pin from the FPGA board and the supply pin (VCC) from a separate power supply. If you servo supports 7.4V, you can also power it directly with a 2 cell LiPo pack. If it’s only 5V, which is the case for most hobby servos) you can use a wall socket adapter or for testing, a bench power supply.

    I hope it helps!

    /Thomas

  21. farah says:

    i have spartan 3e and want to rotate servo hitec motor hs-422 … i do not have the jtag cable ,…. on which pin i should give the pwm signal then……..can i give it on j2 or j4???

  22. Alex says:

    Ty for the answer, i’ve tested with wall power socket for all 5 servos and they are shaky. The servos run at 6v isint it risky to have commong GND with fpga?

  23. I don’t know exactly what pins you can use for I/O, you should check the datasheet for your particular chip and find a pin that you can use for at least 3.3v logic. Otherwise you would need some buffers to get the signals up to somewhere between 3.3v and 5v for the servo to accept the signal.

    If I remember correctly, the HS-422 is a standard analog servo, so the above should also be valid for that one.

    Yo don’t need a JTAG cable for controlling the servo, but might need it to program the FPGA, unless your board has something else for doing that.

  24. Hannes says:

    What is the function of the -2 in the if-statement on line 27 in the ServoDriver file?

    Thanks for your help.

  25. To be honest, I’m afraid that it’s just some left over from a bit of tweaking that changes how the clock is divided down. It’s the same as changing ’63’ to ’61’ on line 15 in the same file. I don’t remember having to fiddle with the values, but it’s some time ago since I did this.

    You should be able to just remove the ‘-2′ and do any tweaking on the ClockDiv constant declaration.

    /Thomas

  26. Sinan says:

    How can I write the ucf file for basys2 board?

  27. Haimanot says:

    Hello,

    how to write VHDL code for position control of 5 (five) DC Servo Motor

  28. Hi Haimanot,

    The easiest would probably be to simply copy and paste 5 times and just rename so that every servo has their own code/circuitry. There might be a pretty way of writing that in VHDL, but i’s not something I can remember at the moment, sorry.

    If the servos are moving together and not individually, you could probably optimize it a bit, but it all depends on the use case.

    Hope it helps,

    /Thomas

  29. ِArwa M.Abass says:

    Hello,

    I would like to know when I want to control servo by using one button in specific angle?,and i would like to know when 000000 is 0 ms ,and when 010101010 is ?? what
    to make table to all possible probability from 00000000to11111111
    thank you,

  30. Hi Arwa,

    I’ll need to have a look and play a bit with the code, but I’ll get back to you soon.

    /Thomas

  31. Rana Hamza says:

    I want to control the load of home appliances using fpga.I want to know how to make the code

  32. Hi Rana,

    That is way too generic to be able to give you a meaningful answer. Explain a bit more and I’ll try to point you in the right direction.

    /Thomas

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>