Generando tren de pulsos de frecuencia variable en un FPGA

5

Estoy trabajando en la generación de un tren de pulsos para controlar un motor que acepta un tren de pulsos como entrada. Cada pulso corresponde a un incremento de movimiento preestablecido; Puedo establecer un pulso igual a 1/1000 grados (o lo que sea), y si le envío 20 pulsos, el motor se moverá 20/1000 grados.

El software que realiza todo el trabajo pesado y determina dónde debe ordenarse el motor para ir en cualquier momento dado está programado en LabVIEW. Este programa luego envía comandos de posición y velocidad (como enteros de 32 bits) a un FPGA, que me gustaría usar para generar una serie de pulsos para decirle al motor qué tan lejos y qué tan rápido debe moverse. Tengo un generador de impulsos simple que solo emite el número requerido de impulsos a la velocidad de reloj del FPGA (vea el diagrama a continuación). ¿Cómo puedo controlar la velocidad de estos pulsos en mi FPGA?

Estoy usando un FPGA de Altera programado en Quartus II v9.0.

simular este circuito : esquema creado usando CircuitLab

Observe el terminal inversor para a = b? en el comparador. El FPGA luego emitirá los valores de pulse y sign para decirle a mi motor qué tan lejos girar y en qué dirección. Las entradas al FPGA son el número entero de pulsos que queremos generar, ref[31..00] , y un indicador de escritura booleano, writeF . Un solo programa controla varios motores, por lo que es necesario especificar cuándo los datos en el bus ref[31..00] corresponden a un motor en particular. El bit más significativo del valor de referencia controlará la dirección del movimiento, por lo que se usa err31 como entrada para el terminal updown .

Como puede ver, el contador está contando el número de pulsos generados, utilizando pulse como su entrada de reloj, pero pulse solo se genera a la velocidad de reloj del FPGA. Dada una entrada adicional a mi FPGA para controlar la frecuencia del pulso, ¿puedo hacer que la frecuencia del pulso sea variable?

EDITAR: cambié mi circuito para que el reloj del sistema ingrese a la entrada clock de mi contador, y mi salida pulse se use como habilitación del reloj ( clock_en ) señal a este contador. Anteriormente tenía mi salida pulse conectada directamente a mi entrada clock , lo cual es potencialmente malo. Publicaré mis hallazgos aquí cuando haya implementado sugerencias.

Solución de contador variable VHDL

Estoy tratando de implementar el enfoque de David Kessner usando VHDL. Básicamente, estoy creando un contador que puede incrementarse en números distintos a 1, y estoy usando la rotación de este contador para determinar cuándo debo generar un pulso. El código se ve así hasta ahora:

--****************************************************************************
-- 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;

Tengo la intención de emitir el MSB directamente o tomar el MSB de este contador ( msb_o(k) ), pasarlo a través de un flip-flop DQ de un solo bit para que también tenga msb_o(k-1) y emitir un pulso cada vez que mi contador de variables se desplaza ejecutando:

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

donde ~ denota NOT lógico, y * denota AND lógico. Este es el primer programa de VHDL que he escrito, y lo escribí en gran parte usando this , este y this . ¿Alguien tiene alguna recomendación sobre cómo podría mejorar mi código? Desafortunadamente, no estoy recibiendo ningún impulso de mi FPGA.

EDITAR: se actualizó el código VHDL a la implementación actual (2013-08-12). También agregue este libro gratuito a la lista de referencias.

EDIT 2: actualicé mi código a la versión de trabajo (final).

    
pregunta Engineero

4 respuestas

5

Lo que quiere hacer es llamarse "Oscilador", o NCO, de control numérico. Funciona así ...

Cree un contador que pueda aumentar en valores distintos de 1. Las entradas a este contador son el reloj maestro y un valor para contar (din). Para cada borde de reloj, cuente < = cuenta + din. El número de bits en din es el mismo que el número de bits en el contador. El valor de conteo real se puede usar para muchas cosas útiles, pero lo que quieres hacer es super simple.

Desea detectar cada vez que el contador se da vuelta y envía un impulso a su motor cuando eso sucede. Haga esto tomando el bit más significativo del contador y ejecutándolo en un solo flip-flop para demorarlo un reloj. Ahora tienes dos señales que llamaré MSB y MSB_Previous. Sabe si el contador se ha volcado porque MSB = 0 y MSB_Prev = 1. Cuando esa condición sea verdadera, envíe un impulso al motor.

Para establecer la frecuencia del pulso, la fórmula es la siguiente: pulse_rate = main_clk_freq * inc_value / 2 ^ n_bits

Donde inc_value es el valor por el cual el contador se incrementa y n_bits es el número de bits en el contador.

Una cosa importante a tener en cuenta es que agregar bits al contador no cambia el rango de la frecuencia de salida, es decir, siempre 0 Hz a la mitad de main_clk_freq. Pero sí cambia la precisión con la que puede generar la frecuencia deseada. Hay muchas posibilidades de que no necesite 32 bits para este contador, y que tal vez solo sean suficientes de 10 a 16 bits.

Este método de generación de pulsos es bueno porque es muy fácil, la lógica es pequeña y rápida, y a menudo puede generar frecuencias con mayor precisión y con mayor flexibilidad que el tipo de diseño de contador + comparador que tiene en su pregunta.

La razón por la que la lógica es más pequeña no es solo porque puede funcionar con un contador más pequeño, sino que no tiene que comparar toda la salida del contador. Solo necesitas la parte superior. Además, la comparación de dos grandes números en un FPGA generalmente requiere una gran cantidad de LUT. La comparación de dos números de 32 bits requeriría 21 LUT de 4 entradas y 3 niveles lógicos, mientras que el diseño NCO requiere 1 LUT, 2 Flip-Flops y solo 1 nivel lógico. (Ignoro el contador, ya que es básicamente el mismo para ambos diseños). El enfoque de NCO es mucho más pequeño, más rápido, mucho más simple y produce mejores resultados.

Actualización: un enfoque alternativo para hacer el detector de vuelco es simplemente enviar el MSB del contador al motor. Si hace esto, la señal que va al motor siempre será un ciclo de trabajo de 50/50. Elegir el mejor enfoque depende de qué tipo de pulso necesita su motor.

Actualización: Aquí hay un fragmento de código VHDL para hacer el 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);
    
respondido por el user3624
3

Supongo que esto es más o menos el comportamiento funcional que desea implementar:

procedure generate_pulse_train( signal write : in std_logic; 
                                signal pulse : out std_logic;
                                constant t_pulse : time; 
                                signal n_pulses : in natural) is
begin
     wait until write = '1';

     for n in 0 to n_pulses-1 loop
         pulse <= '1';
         wait for t_pulse/2;
         pulse <= '0';
         wait for t_pulse/2;
     end loop;
end procedure;

A menos que tenga requisitos muy estrictos para la temporización del reloj de impulsos, recomendaría una máquina de estados simple para generar el tren de impulsos en lugar de ajustar el reloj. Para modificar la frecuencia del reloj durante el tiempo de ejecución, tendría que usar un PLL configurable, pero si la frecuencia del reloj de pulsos es igual o menor que la del reloj del sistema, puede usarla para generar el reloj de pulsos.

Un ejemplo de tal máquina de estado podría ser, por ejemplo:

pr_fsm : process(clk) is
begin
if rising_edge(clk) then
   case state is
       when IDLE =>
           scaler <= pulse_period/2 ;
           pulse_counter <= n_pulses;
           pulse  <= '0';

           if writeF = '1' then
               state <= ACTIVE;
           end if; 

       when ACTIVE =>
           if scaler = 0 then
               scaler <= pulse_length;

               pulse <= not pulse;

               if pulse_counter = 0 then
                   state <= IDLE;
               else
                   pulse_counter <= pulse_counter - 1;
               end if;

           else
               scaler <= scaler - 1;
           end if;
   end case;

end if;
end process pr_fsm;
    
respondido por el trondd
2

Has cerrado tu reloj, lo cual es algo muy malo de hacer a menos que sepas lo que estás haciendo. La misma señal de reloj debe ir a todas las entradas de reloj de sus circuitos.

Debe usar su señal de pulso como un reloj habilitar para su contador; de esa manera, todo es sincrónico y su contador solo cuenta cuando el pulso es alto.

Su generador de impulsos puede operar a otras velocidades, por debajo de la velocidad del reloj principal, al tener otro contador que emite un solo pulso de ancho de reloj cada vez que se ajusta. Al aumentar el recuento de terminales de este contador, aumenta la duración entre pulsos y movimientos más lentos de su motor.

    
respondido por el Martin Thompson
1

Hay varias formas de producir una frecuencia que es un submúltiplo ajustable (posiblemente fraccionario) de un reloj de entrada. Un método popular para permitir cualquier fracción de 1/2 ^ N a 1- (1/2 ^ N) es tener un registro programable de N bits para seleccionar la frecuencia deseada, y un registro de N bits llamado acumulador de fase. Cada ciclo de reloj, agregue el valor de frecuencia al acumulador de fase y emita un impulso si hay un acarreo. Si los valores son 16 bits y el valor de frecuencia es 573, entonces habrá 573 pulsos de salida más o menos espaciados por igual cada 65,536 pulsos de entrada. Los cambios en la frecuencia solicitada afectarán "suavemente" a la salida.

Un enfoque alternativo que puede reducir la cantidad de circuitos requeridos, especialmente si un chip tendrá que manejar muchas frecuencias de salida programables independientemente, es tener un contador con circuitos en cada bit para generar un pulso cada vez que un bit en particular es va a cambiar de cero a uno (la salida controlada al LSB del contador pulsará la mitad de los ciclos, la siguiente salida pulsará la mitad de los ciclos donde el LSB no lo hizo, y cada salida, a su vez, pulsará en la mitad de los ciclos donde no lo hace ninguno de los más bajos. Si una AND es la salida del MSB de la "frecuencia deseada", se registra el LSB del circuito mencionado anteriormente, y los bits restantes del registro de frecuencia en secuencia descendente con bits del circuito mencionado en secuencia ascendente, entonces cada vez que el contador envuelva la salida habrá enviado el número apropiado de pulsos. Tenga en cuenta que cuando se utiliza este enfoque, la salida tendrá más fluctuación que con el appr acumulador de fase. oach, pero la cantidad de circuitos requeridos para cada salida puede reducirse. Tenga en cuenta también que los cambios en la frecuencia programada deben sincronizarse con los tiempos que el temporizador ajusta, o de lo contrario, podrían causar interrupciones en la salida.

Otro enfoque, que es algo así como un híbrido entre los dos primeros, es tener un contador diseñado de modo que cierta entrada lo haga "detenerse" durante un ciclo, y cablear un circuito similar al segundo de arriba para Ese circuito de puesto. Si uno tiene un contador de 8 bits, el valor programado en el registro de frecuencia hará que se detenga entre una y 255 veces cada 256 veces que el contador avanza, lo que hace que la velocidad de ajuste del contador sea ajustable de 256 a 511. Si uno usa un multiplexor de 8 vías, por lo tanto, uno puede tener una relación de división de K / 2 ^ N donde K es 256-511 y N es cero a siete. Tenga en cuenta que los "toques" de orden inferior del contador tendrán mucha fluctuación, pero los de orden superior serán mucho más limpios.

    
respondido por el supercat

Lea otras preguntas en las etiquetas