Source

License

Index

Quadrature Decoder

Decodes a pair of digital signals in quadrature (offset by 90deg), which express the direction of motion. Outputs three sets of pulses: step, direction, and error. The signals are valid when step is high, unless error is also high.

This is an alternate implementation which does not use an FSM, but instead detects edges and distinguishes direction based on the state of the other signals at the time.

The clock may run at any speed, so long as it can properly sample A and B, thus the minimum clock speed is 2x the input toggle rate.

Theory of Operation

A quadrature waveform has two signals, A and B, one always 90deg out of phase with the other. If A leads B, when the edges of A arrive before the edges of B, then we define that as forward motion. If B leads A, we define that as backwards motion. Each edge indicates a step has been taken. If there are no edges, there is no motion. Edges never happen at the same time, which is an error condition.

If A and B are external signals, they must first be synchronized to the clock and debounced, if generated mechanically, using a Debouncer. Any two edges must arrive at least one clock cycle apart, else they happen at the same time, which is an error condition.

The following waveform diagram shows 6 steps of forward motion, followed by a brief pause and 7 steps of backwards motion. Each of the edges in a cycle of A and B, in each direction, are labelled in sequence, starting with the first rising edge of each cycle in each direction.

    F0  F2              B1  B3
     ---     -------     ---
 A _|   |___|       |___|   |___

      F1  F3          B0  B2
       ---     ---     ---     ---
 B ___|   |___|   |___|   |___|

Stuck-At Faults

Note that there is one error condition that cannot be detected directly: if either the A or B input gets stuck, the other input will keep toggling and generate alternating forward and backward edges. This cannot be disinguished from a change in direction, as shown in the middle of the diagram, where A is constant while B still has edges.

Detecting this kind of error must be done at a higher level, such as sending a command to move in a certain direction, but not seeing that motion on the quadrature inputs, either because the device is physically stuck and no edges are seen, or only one set of edges are seen, decoding as minimal back-and-forth movement. The frequency of the edges would report the speed of motion at least, confirming that motion is happening and that a quadrature signal is stuck.

Ports, Parameters, and Constants

`default_nettype none

module Quadrature_Decoder
(
    input   wire    clock,
    input   wire    A,          // Leading A means forward
    input   wire    B,          // Leading B means backwards
    output  reg     step,       // Outputs valid while high
    output  reg     direction,  // 0/1 -> backwards/forwards
    output  reg     error       // Overrides step
);

There are 4 possible forward or backward edges within the combined cycles of A and B.

    localparam EDGE_COUNT = 4;
    localparam EDGES_ZERO = {EDGE_COUNT{1'b0}};

    initial begin
        step        = 1'b0;
        direction   = 1'b0;
        error       = 1'b0;
    end

Positive and Negative Edges on A

    wire A_posedge;
    wire A_negedge;

    Pulse_Generator
    A_rising
    (
        .clock              (clock),
        .level_in           (A),
        .pulse_posedge_out  (A_posedge),
        // verilator lint_off PINCONNECTEMPTY
        .pulse_negedge_out  (),
        .pulse_anyedge_out  ()
        // verilator lint_off PINCONNECTEMPTY
    );

    Pulse_Generator
    A_falling
    (
        .clock              (clock),
        .level_in           (A),
        .pulse_negedge_out  (A_negedge),
        // verilator lint_off PINCONNECTEMPTY
        .pulse_posedge_out  (),
        .pulse_anyedge_out  ()
        // verilator lint_off PINCONNECTEMPTY
    );

Positive and Negative Edges on B

    wire B_posedge;
    wire B_negedge;

    Pulse_Generator
    B_rising
    (
        .clock              (clock),
        .level_in           (A),
        .pulse_posedge_out  (B_posedge),
        // verilator lint_off PINCONNECTEMPTY
        .pulse_negedge_out  (),
        .pulse_anyedge_out  ()
        // verilator lint_off PINCONNECTEMPTY
    );

    Pulse_Generator
    B_falling
    (
        .clock              (clock),
        .level_in           (A),
        .pulse_negedge_out  (B_negedge),
        // verilator lint_off PINCONNECTEMPTY
        .pulse_posedge_out  (),
        .pulse_anyedge_out  ()
        // verilator lint_off PINCONNECTEMPTY
    );

Forward and Backward Edges

There are 4 possible forward and backward edges, defined by when one signal has a positive or negative edge, and the state of the other signal at that time. See the waveform diagram above for reference.

    reg [EDGE_COUNT-1:0] forward_edges  = EDGES_ZERO;
    reg [EDGE_COUNT-1:0] backward_edges = EDGES_ZERO;

    always @(*) begin
        forward_edges[0]  = (A_posedge == 1'b1) && (B == 1'b0);
        forward_edges[1]  = (B_posedge == 1'b1) && (A == 1'b1);
        forward_edges[2]  = (A_negedge == 1'b1) && (B == 1'b1);
        forward_edges[3]  = (B_negedge == 1'b1) && (A == 1'b0);

        backward_edges[0] = (B_posedge == 1'b1) && (A == 1'b0);
        backward_edges[1] = (A_posedge == 1'b1) && (B == 1'b1);
        backward_edges[2] = (B_negedge == 1'b1) && (A == 1'b1);
        backward_edges[3] = (A_negedge == 1'b1) && (B == 1'b0);
    end

Forward and Backward Steps

Let's reduce the edges from each direction to one signal which tells us if a step has been taken either forward or backward. Since no two edges can happen at the same time during normal operation, these two signals can never be active at the same time, which is a very useful property...

    reg forward_step  = 1'b0;
    reg backward_step = 1'b0;

    always @(*) begin
        forward_step  = (forward_edges  != EDGES_ZERO);
        backward_step = (backward_edges != EDGES_ZERO);
    end

Decoded Outputs

The step signal goes high whenever there is a forward or backward step.

The direction signal is high for a forward step, and low for a backward step, so we only need to use the forward step since they are mutually exclusive, as shown above.

Finally, if both a forward and backward step happen at the same time, this is impossible, and error is pulsed high. The direction output will signal forward, but it is unknown if it is correct. The step output will signal a step, but it is unknown if one or more steps have occured.

    always @(*) begin
        step        = (forward_step == 1'b1) || (backward_step == 1'b1);
        direction   = (forward_step == 1'b1);
        error       = (forward_step == 1'b1) && (backward_step == 1'b1);
    end

endmodule

Back to FPGA Design Elements

fpgacpu.ca