Electronic – Generating pulse train of varying frequency on an FPGA

fpgaintel-fpgaquartus-iistepper motorvhdl

I am working on generating a pulse train to control a motor that accepts a pulse train as an input. Each pulse corresponds to a pre-set movement increment; I can set one pulse equal to 1/1000 degree (or whatever), and if I send it 20 pulses, the motor will move 20/1000 degree.

The software that performs all the heavy lifting and determines where the motor needs to be commanded to go at any given time is programmed in LabVIEW. This program then sends position and speed commands (as 32-bit integers) to an FPGA, which I would like to use to generate a series of pulses to tell the motor how far and how fast to move. I have a simple pulse generator that just puts out the required number of pulses at the FPGA's clock speed (see diagram below). How can I control the speed of these pulses in my FPGA?

I am using an Altera FPGA programmed in Quartus II v9.0.

schematic

simulate this circuit – Schematic created using CircuitLab

Note the inverting terminal for a = b? on the comparator. The FPGA will then output the values of pulse and sign to tell my motor how far to turn and in what direction. Inputs to the FPGA are the integer number of pulses we want to generate, ref[31..00], and a boolean write flag, writeF. Multiple motors are controlled by one program, thus the need to specify when the data on the bus ref[31..00] is for a particular motor. The most significant bit of the reference value will control the direction of movement, thus err31 is used as the input to the updown terminal.

As you can see, the counter is counting the number of pulses generated, using pulse as its clock input, but pulse is only being generated at the FPGA's clock speed. Given an additional input to my FPGA to control pulse rate, can I make the pulse rate variable?

EDIT: I changed my circuit so that the system clock is going in to the clock input of my counter, and my pulse output is being used as the clock enable (clock_en) signal to this counter. Previously I had my pulse output plugged straight in to my clock input, which is potentially bad. I will post my findings here when I have implemented suggestions.

VHDL Variable Counter Solution

I am trying to implement David Kessner's approach using VHDL. Basically I am creating a counter that can increment by numbers other than 1, and using the rollover of this counter to determine when I should generate a pulse. The code looks like this so far:

--****************************************************************************
-- Load required libraries
--****************************************************************************
library ieee;
    use ieee.std_logic_1164.all;
    use ieee.numeric_std.all;

--****************************************************************************
-- Define the inputs, outputs, and parameters
--****************************************************************************
entity var_count is

    generic(N: integer :=32);               -- for generic counter size
    port(
            inc_i       : in    std_logic_vector(N-1 downto 0);
            load_i      : in    std_logic;
            clk_i       : in    std_logic;
            clear_i     : in    std_logic;
            clk_en_i    : in    std_logic;
            count_en_i  : in    std_logic;
            msb_o       : out   std_logic
        );

end var_count;

--****************************************************************************
-- Define the behavior of the counter
--****************************************************************************
architecture behavior of var_count is

    -- Define our count variable. No need to initialize in VHDL.
    signal count : unsigned(N-1 downto 0) := to_unsigned(0, N);
    signal incr  : unsigned(N-1 downto 0) := to_unsigned(0, N);

begin   
    -- Define our clock process
    clk_proc : process(clk_i, clear_i, load_i)
    begin
        -- Asynchronous clear
        if clear_i = '1' then
            count <= to_unsigned(0, N);
        end if;

        -- Asynchronous load
        if load_i = '1' then
            incr <= unsigned(inc_i);
        end if;

        -- Define processes synch'd with clock.
        if rising_edge(clk_i) and clk_en_i = '1' then
            if count_en_i = '1' then            -- increment the counter
                -- count <= count + unsigned(inc_i);
                count <= count + incr;
            end if;
        end if;     
    end process clk_proc;

    -- Output the MSB for the sake of generating a nice easy square wave.
    msb_o <= count(count'left);

end behavior;

I intend to either output the MSB directly, or to take the MSB from the this counter (msb_o(k)), pass it through a single-bit D-Q flip flop so that I also have msb_o(k-1), and output a pulse every time my variable counter rolls over by executing:

PULSE = ~msb_o(k) * msb_o(k-1)

where ~ denotes logical NOT, and * denotes logical AND. This is the first VHDL program I have written, and I wrote it largely using this, this, and this. Does anybody have any recommendations as to how I could improve my code? Unfortunately I am not getting any pulses out of my FPGA still.

EDIT: Updated the VHDL code to the current implementation (2013-08-12). Also adding this free book to the list of references.

EDIT 2: Updated my code to the (final) working version.

Best Answer

What you want to do is called a Numerically Controlled "Oscillator", or NCO. It works like this...

Create a counter that can increment by values other than 1. The inputs to this counter are the master clock, and a value to count by (din). For each clock edge, count <= count + din. The number of bits in din is the same as the number of bits in the counter. The actual count value can be used for many useful things, but what you want to do is super simple.

You want to detect every time the counter rolls over, and output a pulse to your motor when that happens. Do this by taking the most significant bit of the counter and running it through a single flip-flop to delay it by one clock. Now you have two signals that I'll call MSB, and MSB_Previous. You know if the counter has rolled over because MSB=0 and MSB_Prev=1. When that condition is true, send a pulse to the motor.

To set the pulse frequency, the formula is this: pulse_rate = main_clk_freq * inc_value/2^n_bits

Where inc_value is the value that the counter is being incremented by and n_bits is the number of bits in the counter.

An important thing to note is that adding bits to the counter does not change the range of the output frequency-- that is always 0 Hz to half of main_clk_freq. But it does change the accuracy that you can generate the desired frequency. Odds are high that you won't need 32-bits for this counter, and that maybe just 10 to 16 bits will be enough.

This method of generating pulses is nice because it is super easy, the logic is small and fast, and it can often generate frequencies more accurately and with better flexibility than the type of counter+comparator design that you have in your question.

The reason why the logic is smaller is not only because you can get by with a smaller counter, but you do not have to compare the entire output of the counter. You only need the top bit. Also, comparing two large numbers in an FPGA usually requires a lot of LUTs. Comparing two 32-bit numbers would require 21 4-Input LUTs and 3 logic levels, where as the NCO design requires 1 LUT, 2 Flip-Flops, and only 1 logic level. (I'm ignoring the counter, since it is basically the same for both designs.) The NCO approach is much smaller, much faster, much simpler, and yields better results.

Update: An alternative approach to making the rollover detector is to simply send out the MSB of the counter to the motor. If you do this, the signal going to the motor will always be a 50/50 duty cycle. Choosing the best approach depends on what kind of pulse your motor needs.

Update: Here is a VHDL code snippet for doing the NCO.

signal count :std_logic_vector (15 downto 0) := (others=>'0);
signal inc   :std_logic_vector (15 downto 0) := (others=>'0);
signal pulse :std_logic := '0';

. . .

process (clk)
begin
  if rising_edge(clk) then
    count <= count + inc;
  end if;
end process;

pulse <= count(count'high);