Using a quadrature encoder as input to FPGA

This time, instead of using buttons to control the servo, we will use a quadrature encoder (I got it from Sparkfun, sku: COM-09117, but you should be able to use more or less any quadrature encoder).

The QuadratureDecoder entity
This entity reads the signals from the quadrature encoder and maintains a counter, that is incremented or decremented depending on which way the knob is rotated.

The VHDL below is more or less a translation of the Verilog example at fpga4fun.com, where you can also find a description of the signals from the quadrature encoder.

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
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 QuadratureDecoder is
    Port ( QuadA : in  STD_LOGIC;
           QuadB : in  STD_LOGIC;
           Clk : in  STD_LOGIC;
           Position : out  STD_LOGIC_VECTOR (7 downto 0));
end QuadratureDecoder;
 
architecture Behavioral of QuadratureDecoder is
 
signal QuadA_Delayed: unsigned(2 downto 0) := "000";
signal QuadB_Delayed: unsigned(2 downto 0) := "000";
 
signal Count_Enable: STD_LOGIC;
signal Count_Direction: STD_LOGIC;
 
signal Count: unsigned(7 downto 0) := "00000000";
 
begin
 
process (Clk)
begin
	if Clk='1' and Clk'event then
		QuadA_Delayed <= (QuadA_Delayed(1), QuadA_Delayed(0), QuadA);
		QuadB_Delayed <= (QuadB_Delayed(1), QuadB_Delayed(0), QuadB);
		if Count_Enable='1' then
			if Count_Direction='1' then
				Count <= Count + 1;
				Position <= conv_std_logic_vector(Count, 8);
			else
				Count <= Count - 1;
				Position <= conv_std_logic_vector(Count, 8);
			end if;
		end if;
	end if;
end process;
 
Count_Enable <= QuadA_Delayed(1) xor QuadA_Delayed(2) xor QuadB_Delayed(1)
				xor QuadB_Delayed(2);
Count_Direction <= QuadA_Delayed(1) xor QuadB_Delayed(2);
 
end Behavioral;

Modified version of the top module
This is the same top module as used with the previous post, but with the button controller exchanged with the above quadrature decoder.

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;
           QuadA : in STD_LOGIC;
           QuadB : 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);
QuadDecoder1: entity QuadratureDecoder port map (QuadA, QuadB, Clk, Position);
 
end Behavioral;

The modified UCF file for the AVNET Xilinx® Spartan®-3A Evaluation Kit
Here I just removed the two buttons and added the two inputs from the quadrature encoder. To keep it simple on the hardware side, I enabled pull-up on both of those I/O’s, and connected the shared pin from the quadrature encoder to GND on the board. The servo is connected with negative to GND, positive to +5v and signal to D10.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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 "Servo" LOC = D10;
NET "QuadA" LOC = C5;
NET "QuadB" LOC = C6;
 
# PlanAhead Generated IO constraints 
NET "Clk" IOSTANDARD = LVCMOS33;
NET "Servo" IOSTANDARD = LVCMOS33;
NET "QuadA" IOSTANDARD = LVCMOS33;
NET "QuadB" IOSTANDARD = LVCMOS33;
 
# PlanAhead Generated IO constraints 
NET "QuadA" PULLUP;
NET "QuadB" PULLUP;

Below is a video showing the use of the quadrature encoder to control the servo position

2 thoughts on “Using a quadrature encoder as input to FPGA”

  1. Howard Leamon says:

    If you are bothering to use the ieee.numeric_std.all why (oh, why) are using the non-standard (despite its name) std_logic_unsigned.all and the horrible conv_std_logic_vector? Addition and casting of unsigned vectors are already handled in the ieee.numeric_std.all!

    Although the functional result is the same, better code would be:

    signal count : integer range 0 to 2**position’LENGTH-1 := 0;
    .
    .
    position <= std_logic_vector(to_unsigned(count, position'LENGTH));

    It would even be possible to use position as an unsigned on the port (perfectly allowable). Only Xilinx seems to insist on std_logic_vector on the port because otherwise it interferes with their autogeneration of test benches and they're too lazy to fix that.

  2. Hi Howard,

    The VHDL have been written some years ago, and later modified a bit to work with newer Xilinx tools, but as you are pointing out, I have just pieced together something that works and is somewhat understandable.

    I appreciate your feedback, and I’ll try to remember your commnets next time I’m playing with VHDL and FPGA’s

    Thanks!

    /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>