Altera_Forum
Honored Contributor
12 years agoIncremental Quadrature Encoder
I am posting this for comments but also if someone wants to use it...
Below is the code I made for decoding quadrature incremental encoders. Enc A and Enc B are inputs from the encoder. RD is the reading strobe - when high, current counter value will be latched for the output. In this way, other components can strobe RD input to get current counter value and process it while internal counter is being updated. Counter output is the current value (after RD strobing). CLRCNT - put on high, clears current counter value back to 0. Coeff is the input coefficient used in case of for example counts to mm conversion. So every coeff amount of counts, a pulse will be output. Ch_Coeff input when high would latch coeff input into internal registers. Pls output is output of a encoder pulse every coeff intervals. So for example if one needs to do some task every 1mm of encoder travel, coeff value should be equal to number of pulses of mm. If one wants to do some task every 2mm, coeff would be number of counts per 2mm. dbg_cnt is just a debug counter useful in simulation to see how internal counter gets updated every clk cycle based on encoder inputs.
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
ENTITY EncoderCounter IS
GENERIC
(
CNTR_SZ : INTEGER := 32;
COEFF_SZ : INTEGER := 8
);
PORT
(
clk : IN STD_LOGIC;
rstn : IN STD_LOGIC;
clrcnt : IN STD_LOGIC;
encA : IN STD_LOGIC;
encB : IN STD_LOGIC;
rd : IN STD_LOGIC;
coeff : IN STD_LOGIC_VECTOR(COEFF_SZ-1 DOWNTO 0);
ch_coeff : IN STD_LOGIC;
pls : OUT STD_LOGIC;
counter : OUT STD_LOGIC_VECTOR(CNTR_SZ-1 DOWNTO 0);
dbg_cnt : OUT STD_LOGIC_VECTOR(CNTR_SZ-1 DOWNTO 0)
);
END ENTITY;
ARCHITECTURE behv OF EncoderCounter IS
signal temp_encA : STD_LOGIC_VECTOR(2 DOWNTO 0);
signal temp_encB : STD_LOGIC_VECTOR(2 DOWNTO 0);
signal temp_counter : SIGNED(CNTR_SZ-1 DOWNTO 0);
signal counter_reg : SIGNED(CNTR_SZ-1 DOWNTO 0);
signal sync_encA : STD_LOGIC;
-- signal sync_encB : STD_LOGIC;
-- signal rising_encA : STD_LOGIC;
-- signal falling_encA : STD_LOGIC;
signal rising_encB : STD_LOGIC;
-- signal falling_encB : STD_LOGIC;
signal coeff_val : UNSIGNED(COEFF_SZ-1 DOWNTO 0);
signal out_pls : STD_LOGIC;
BEGIN
PROCESS(clk, rstn)
BEGIN
IF(rstn = '0') THEN
coeff_val <= (others => '0');
ELSIF(rising_edge(clk)) THEN
IF(ch_coeff = '1') THEN
coeff_val <= unsigned(coeff);
END IF;
END IF;
END PROCESS;
dbg_cnt <= std_logic_vector(temp_counter);
-- Synchronize encoder A and B inputs and also detect falling and rising edges
-- (1) used as 2-DFF synchronizer output for each signal
-- (2) used to detect rising/falling edges
PROCESS(clk, rstn)
BEGIN
IF(rstn = '0') THEN
temp_encA <= (others=>'0');
temp_encB <= (others=>'0');
ELSIF(rising_edge(clk)) THEN
temp_encA <= temp_encA(1 DOWNTO 0) & encA;
temp_encB <= temp_encB(1 DOWNTO 0) & encB;
END IF;
END PROCESS;
sync_encA <= temp_encA(1);
-- sync_encB <= temp_encB(1);
-- rising_encA <= '1' WHEN temp_encA(2) = '0' AND temp_encA(1) = '1' ELSE '0';
rising_encB <= '1' WHEN temp_encB(2) = '0' AND temp_encB(1) = '1' ELSE '0';
-- falling_encA <= '1' WHEN temp_encA(2) = '1' AND temp_encA(1) = '0' ELSE '0';
-- falling_encB <= '1' WHEN temp_encB(2) = '1' AND temp_encB(1) = '0' ELSE '0';
-- 4x decoding and detecting pulse rising edge
-- pulse <= rising_encA XOR rising_encB XOR falling_encA XOR falling_encB;
-- updating counter
-- clrcnt is used to clear the counter back to 0
-- for that we need to use clk and not pulse in the process
-- if clrn is not needed - pulse can be used as clock to update the counter
PROCESS(clk, rstn, clrcnt)
BEGIN
IF(rstn = '0') THEN
temp_counter <= (others=>'0');
ELSIF(rising_edge(clk)) THEN
IF(clrcnt = '1') THEN
temp_counter <= (others => '0');
ELSIF(rising_encB = '1' AND sync_encA = '1') THEN
temp_counter <= temp_counter + 1;
ELSIF(rising_encB = '1' AND sync_encA = '0') THEN
temp_counter <= temp_counter - 1;
ELSE
temp_counter <= temp_counter + 0;
END IF;
END IF;
END PROCESS;
-- Outputs
-- RD pulse latches the counter value to the output
-- so it can be processed as needed elsewhere while counter is still being updated.
PROCESS(clk, rstn, rd)
BEGIN
IF(rstn = '0') THEN
counter <= (others => '0');
counter_reg <= (others => '0');
ELSIF(rising_edge(clk)) THEN
IF(rd = '1') THEN
counter_reg <= temp_counter;
counter <= std_logic_vector(temp_counter);
ELSE
counter <= std_logic_vector(counter_reg);
END IF;
END IF;
END PROCESS;
-- Will output a single pulse when a number of pulses from encoder is reached
-- it can be used for counts to mm conversion when a number of pulses is known (= value should be put into coeff)
-- pulse will be single clk long
PROCESS(clk, rising_encB, rstn)
VARIABLE cnt : INTEGER := 0;
BEGIN
IF(rstn = '0') THEN
cnt := 0;
out_pls <= '0';
ELSIF(rising_edge(clk)) THEN
IF(rising_encB = '1') THEN
IF(cnt = coeff_val) THEN
out_pls <= '1';
cnt := 0;
ELSE
out_pls <= '0';
cnt := cnt + 1;
END IF;
ELSE
out_pls <= '0';
END IF;
END IF;
END PROCESS;
pls <= rising_encB WHEN (coeff_val = 0 OR coeff_val = 1) ELSE out_pls;
END behv;
Below are simulation results: https://www.alteraforum.com/forum/attachment.php?attachmentid=8251 When one wants to use it to find out number of counts per mm following should occur: 1. Put motor axis at some position. 2. Clear current counter value (strobe clrcnt). 3. Send motor to travel some distance (say 100mm). 4. Get counter value. 5. Divide counter value by distance. 6. Put number in 5 into coeff. Then pls would happen every 1mm of distance traveled.