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.
This time, I'm testing it on on a Xilinx Spartan 6 FPGA.
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.
I've adapted the code using info from the following blog post:
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
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.
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.
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.
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;
---------------------------------------------------------------------------------- -- 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;
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.
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.