I've removed the rotary encoder from the scroll button of a defect mouse. Let's see how we can use that in a microcontroller design.

In post 1, I wired up the electronics.

In part 2, I'm capturing the encoder's info on a Cypress PSoC4.

In the third blog, I've used an Arduino.

In number 4, I'm capturing the encoder's info with the eQEP module of a TI Hercules microcontroller.

This time, I'm testing it on on a Xilinx Spartan 6 FPGA.

 

 

 

 

The Code


Disclaimer: I haven't found consensus on how to call the logic for a FPGA.

I started from a project made  by Thomas Flummer on his hackmeister blog in 2010.

He started with a FPGA servo controller that was controlled with two buttons for left and right movement.

Later he added a quadrature encoder module to control the servo position.

And that's the project that I picked up here.

https://www.hackmeister.dk/2010/07/controlling-an-rc-servo-with-an-fpga/

https://www.hackmeister.dk/2010/07/using-a-quadrature-encoder-as-input-to-fpga/

 

I've adapted the code using info from the following blog post:

https://groups.google.com/d/msg/comp.lang.vhdl/nD5V5v_kfLw/at8iEDFsxYcJ

It uses a deprecated math library. I switched all the locations where STD_LOGIC_VECTOR is used in calculations to UNSIGNED.

Locations where the STD_LOGIC_VECTOR is used to contain bit values (e.g. QuadA_Delayed), I have kept as is.

(I'd like a guru to have a look at what I did with PulseCount in ServoDriver.vhd)

 

I've also replaced occurrences of

if Clk='1' and Clk'event then









by

if rising_edge(Clk) then









.

The code is using direct instantiation of entities, in stead of the architecture -> component -> entity -> instance approach.

Because of that, I had to add the "work." qualifier when instantiating modules. I may change that later.

 

johnbeetem advised me to ask michaelkellett for a VHDL review.

Go ahead Michael.

 

Disclaimer 2: The project doesn't deliver correct Servo signals. The PWM frequency isn't 50 Hz, and the pulse width isn't conform the servo PWM specs.

It doesn't matter. The only thing that I want to show here is that I can increase and decrease the duty cycle of the FPGA output with a rotary encoder.

 

fpga xilinx papilio spartan quadrature

 

The design has 3 modules:

  • The ServoDriver: a module that generates a PWM signal with a given duty cycle based upon position info that we pass to it.
  • The QuadratureDecoder: this module picks up movement of the rotary encoder and translates that into  a position.
  • The ServoUpDown: this top module glues the two other modules together. It connects the Encoder position with the Servo position.

 

edit after comments below: I've replaced all tabs in the source by spaces. That resolves the indentation differences between IDE and this blog.

QuadratureDecoder.vhd

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity QuadratureDecoder is
    Port ( QuadA : in  STD_LOGIC;
           QuadB : in  STD_LOGIC;
           Clk : in  STD_LOGIC;
           Position : out  unsigned (7 downto 0));
end QuadratureDecoder;

architecture Behavioral of QuadratureDecoder is

signal QuadA_Delayed: std_logic_vector(2 downto 0) := "000";
signal QuadB_Delayed: std_logic_vector(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 rising_edge(Clk) then
   -- 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 <= Count;
         else
            Count <= Count - 1;
            Position <= Count;
         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;




 

ServoDriver.vhd

----------------------------------------------------------------------------------
-- Company:
-- Engineer:
--
-- Create Date:    16:55:40 07/14/2015
-- Design Name:
-- Module Name:    ServoDriver - Behavioral
-- Project Name:
-- Target Devices:
-- Tool versions:
-- Description:
--
-- Dependencies:
--
-- Revision:
-- Revision 0.01 - File Created
-- Additional Comments:
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity ServoDriver is
    Port ( Clk : in  STD_LOGIC;
           Position : in unsigned (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: unsigned (6 downto 0) := "0000000";

signal PulseCount: unsigned (11 downto 0) := "000000000000";

begin

process (Clk)
begin
   if rising_edge(Clk) then
   -- 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 rising_edge(Clk) then
   -- 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;




 

ServoUpDown.vhd

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.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 : unsigned (7 downto 0);

begin

Servo1: entity work.ServoDriver port map (Clk, Position, Servo);
QuadDecoder1: entity work.QuadratureDecoder port map (QuadA, QuadB, Clk, Position);

end Behavioral;








 

 

 

Wiring the Hardware

 

Look back at my first post to check how to components are wired up.

Then connect the encoder with the Papilio Pro.

 

CONFIG PROHIBIT=P144;
CONFIG PROHIBIT=P69;
CONFIG PROHIBIT=P60;


NET Clk TNM_NET = Clk;
TIMESPEC TS_Clk = PERIOD Clk 31.25ns HIGH 50 %;


NET Clk            LOC="P94"  | IOSTANDARD=LVCMOS33 | PERIOD=31.25ns;             # CLK


NET Servo IOSTANDARD = LVCMOS33;
NET Servo SLEW = FAST;
NET Servo DRIVE = 8;
NET Servo LOC = P48; # A0


NET QuadA IOSTANDARD = LVCMOS33;
NET QuadA SLEW = FAST;
NET QuadA DRIVE = 8;
NET QuadA LOC = P51; # A1


NET QuadB IOSTANDARD = LVCMOS33;
NET QuadB SLEW = FAST;
NET QuadB DRIVE = 8;
NET QuadB LOC = P56; # A2







Ground to Ground, 3.3V to 3.3V,

PINA to A1, PINB to A2. Oscilloscope to A0.

 

fpga rotary_encoder papilio xilinx

 

Load the bitstream to the FPGA and rotate the encoder.

You'll see that the output signal has a constant frequency and that the duty cycle can be controlled by turning the encoder.

 

 

Content
Part 1: Electronics
Part 2: Capturing Input on Cypress PSoC4
Part 3: Capturing Input on an Arduino
Part 4: Capturing Input on a Texas Instruments Hercules LaunchPad with eQEP
Part 5: Capturing Input on an FPGA
Real World Application: Hercules LaunchPad and GaN FETs: Control Big Power with a Flimsy Mouse Scroll Wheel