Forum Discussion

Altera_Forum's avatar
Altera_Forum
Icon for Honored Contributor rankHonored Contributor
12 years ago

Incremental 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.

4 Replies

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    There's room for improvements. A good quadrature encoder is expected to count on any rising or falling input edge, not only rising edge of one input channel. This way, the encoder resolution can be increased by a factor of four.

    Acting on all edges is also necessary to detect overun conditions respectively inplausible input sequences (hardware defects) and flag them as error.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- Quote Start ---

    There's room for improvements. A good quadrature encoder is expected to count on any rising or falling input edge, not only rising edge of one input channel. This way, the encoder resolution can be increased by a factor of four.

    Acting on all edges is also necessary to detect overun conditions respectively inplausible input sequences (hardware defects) and flag them as error.

    --- Quote End ---

    Yes - this would be 4x decoder then.

    I have code shown to detect rising and falling edges of both A and B signals (in posted code it is commented out). It is a matter of extra ELSIF statements in the process to correctly decode should the counter be incremented or decremented at those edges. For example on falling B, if A leads B (and this is declared to be incrementing the counter) then if B='1' and A='0' counter should be incremented and if B='0' and A='1' counter should be decremented.

    I will do an update and post the revision later.

    As for error - it can be detected when in all other cases not covered in the code. For my application in particular, user will know the error....flagging/flashing is not useful.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    This code has 4x counter decoding. Extra IF statements were added in the counter and it also had modified pulse output section.

    
    LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    USE ieee.numeric_std.all;
    ENTITY EncoderDecoder 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 EncoderDecoder 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 pulse : STD_LOGIC;
        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;            
            ELSIF(falling_encB = '1' AND sync_encA = '0') THEN 
              temp_counter <= temp_counter + 1;            
           ELSIF(falling_encB = '1' AND sync_encA = '1') THEN
               temp_counter <= temp_counter - 1;            
            ELSIF(rising_encA = '1' AND sync_encB = '0') THEN 
                temp_counter <= temp_counter + 1;            
            ELSIF(rising_encA = '1' AND sync_encB = '1') THEN
               temp_counter <= temp_counter - 1;            
            ELSIF(falling_encA = '1' AND sync_encB = '1') THEN
                temp_counter <= temp_counter + 1;            
            ELSIF(falling_encA = '1' AND sync_encB = '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, pulse, rstn)
      VARIABLE cnt : INTEGER := 0;
    BEGIN
      IF(rstn = '0') THEN 
         cnt := 0;
          out_pls <= '0';
      ELSIF(rising_edge(clk)) THEN      
        IF(pulse = '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 <= pulse WHEN (coeff_val = 0 OR coeff_val = 1) ELSE out_pls;
    END behv;
    
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    I just realized that the code given had a bug when encoder moved up and down. This one below fixes this and calculates offset from last pulse output. So it is working as to be expected.

    
    LIBRARY ieee;
    USE ieee.std_logic_1164.all;
    USE ieee.numeric_std.all;
    ENTITY EncoderDecoder IS
                
                 GENERIC
                 ( 
                    CNTR_SZ : INTEGER := 32;
                     COEFF_SZ : INTEGER := 16 
                 );
                 
                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 EncoderDecoder 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 pulse : STD_LOGIC;
                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;            
                    ELSIF(falling_encB = '1' AND sync_encA = '0') THEN 
                      temp_counter <= temp_counter + 1;            
                   ELSIF(falling_encB = '1' AND sync_encA = '1') THEN
                       temp_counter <= temp_counter - 1;            
                    ELSIF(rising_encA = '1' AND sync_encB = '0') THEN 
                        temp_counter <= temp_counter + 1;            
                    ELSIF(rising_encA = '1' AND sync_encB = '1') THEN
                       temp_counter <= temp_counter - 1;            
                    ELSIF(falling_encA = '1' AND sync_encB = '1') THEN
                        temp_counter <= temp_counter + 1;            
                    ELSIF(falling_encA = '1' AND sync_encB = '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, pulse, rstn, temp_counter)
              VARIABLE cnt : SIGNED(CNTR_SZ-1 DOWNTO 0) := (others => '0');
                 VARIABLE diff : SIGNED(CNTR_SZ-1 DOWNTO 0) := (others => '0');
            BEGIN
              IF(rstn = '0') THEN 
                  cnt := temp_counter;
                  out_pls <= '0';
              ELSIF(rising_edge(clk)) THEN     
                   diff := abs(cnt - temp_counter);
                   IF(clrcnt = '1') THEN 
                        out_pls <= '0';
                         cnt := temp_counter;
                         diff := (others => '0');
                ELSIF(pulse = '1') THEN 
                    IF(to_integer(diff) >= to_integer(coeff_val)) THEN 
                        out_pls <= '1';
                        cnt := temp_counter;
                    ELSE          
                        out_pls <= '0';                    
                    END IF;      
                ELSIF(pulse = '0' AND clrcnt = '0') THEN
                   out_pls <= '0';
                END IF;
              END IF;                           
            END PROCESS;
            pls <= pulse WHEN (coeff_val = 0 OR coeff_val = 1) ELSE out_pls;
            END behv;