5 votos

Generación de un tren de pulsos de frecuencia variable en una FPGA

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 hacia dónde debe dirigirse el motor en cada momento está programado en LabVIEW. Este programa envía los comandos de posición y velocidad (como enteros de 32 bits) a una FPGA, que me gustaría utilizar para generar una serie de pulsos para decirle al motor lo lejos y lo rápido que debe moverse. Tengo un simple generador de pulsos que simplemente emite el número de pulsos necesarios a la velocidad de reloj de la FPGA (ver diagrama abajo). ¿Cómo puedo controlar la velocidad de estos pulsos en mi FPGA?

Estoy utilizando una FPGA Altera programada en Quartus II v9.0.

schematic

simular este circuito - Esquema creado con CircuitLab

Tenga en cuenta el terminal inversor para a = b? en el comparador. La FPGA emitirá entonces los valores de pulse y sign para indicarle a mi motor cuánto debe girar y en qué dirección. Las entradas a la FPGA son el número entero de pulsos que queremos generar, ref[31..00] y un indicador booleano de escritura, writeF . Un programa controla varios motores, de ahí la necesidad de especificar cuándo los datos del bus ref[31..00] es para un motor en particular. El bit más significativo del valor de referencia controlará la dirección del movimiento, así err31 se utiliza como entrada para el updown terminal.

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

EDITAR: He cambiado mi circuito para que el reloj del sistema entre en el clock entrada de mi contador, y mi pulse se está utilizando como habilitación del reloj ( clock_en ) a este contador. Anteriormente tenía mi pulse salida conectada directamente a mi clock entrada, lo que es potencialmente malo. Voy a publicar mis conclusiones aquí cuando he implementado sugerencias.

Solución de contador variable VHDL

Estoy tratando de implementar el enfoque de David Kessner utilizando VHDL. Básicamente estoy creando un contador que puede incrementarse por números distintos de 1, y utilizando el rollover 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 dar salida al MSB directamente, o tomar el MSB de este contador ( msb_o(k) ), pasarla a través de un flip flop D-Q 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 vuelque al ejecutarse:

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

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

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

EDITAR 2: He actualizado mi código a la versión (final) que funciona.

5voto

Lo que quieres hacer se llama "Oscilador" controlado numéricamente, o NCO. Funciona así...

Crea un contador que pueda incrementarse por valores distintos de 1. Las entradas de este contador son el reloj maestro, y un valor por el que contar (din). Para cada flanco de reloj, count <= count + din. El número de bits de din es el mismo que el número de bits del contador. El valor real del conteo puede ser usado para muchas cosas útiles, pero lo que quieres hacer es súper simple.

Quieres detectar cada vez que el contador se vuelque, y emitir un pulso a tu motor cuando eso ocurra. Hazlo tomando el bit más significativo del contador y pasándolo por un solo flip-flop para retrasarlo un reloj. Ahora tienes dos señales que llamaré MSB, y MSB_Previous. Sabes si el contador se ha volcado porque MSB=0 y MSB_Prev=1. Cuando esa condición es verdadera, envía un pulso al motor.

Para establecer la frecuencia de los pulsos, la fórmula es esta: pulse_rate = main_clk_freq * inc_value/2^n_bits

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

Una cosa importante a tener en cuenta es que la adición de bits al contador no cambia el rango de la frecuencia de salida - que es siempre de 0 Hz a la mitad de main_clk_freq. Pero sí cambia la precisión con la que puedes generar la frecuencia deseada. Lo más probable es que no necesites 32 bits para este contador, y que tal vez sólo 10 o 16 bits sean suficientes.

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

La razón por la que la lógica es más pequeña no es sólo porque puedes arreglártelas con un contador más pequeño, sino que no tienes que comparar toda la salida del contador. Sólo necesitas el bit superior. Además, comparar dos números grandes en una FPGA suele requerir muchas LUTs. Comparar dos números de 32 bits requeriría 21 LUTs de 4 entradas y 3 niveles lógicos, mientras que el diseño del NCO requiere 1 LUT, 2 Flip-Flops, y sólo 1 nivel lógico. (Estoy ignorando el contador, ya que es básicamente el mismo para ambos diseños.) El enfoque NCO es mucho más pequeño, mucho 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 haces esto, la señal que va al motor será siempre un ciclo de trabajo 50/50. La elección del mejor enfoque depende de qué tipo de pulso necesita tu 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);

3voto

Ryan Farley Puntos 7916

Supongo que este es más o menos el comportamiento funcional que quieres 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 tengas requisitos muy estrictos para la sincronización del reloj de pulsos, yo recomendaría una simple máquina de estados para generar el tren de pulsos en lugar de la puerta del reloj. Para alterar la frecuencia del reloj durante el tiempo de ejecución tendrías que usar un PLL configurable, pero si la frecuencia del reloj de pulsos es igual o menor que la del reloj del sistema puedes usarla para generar el reloj de pulsos.

Un ejemplo de esta 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;

2voto

Martin Thompson Puntos 6509

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

Necesitas usar tu señal de pulso como un reloj activar a su contador - de esa manera todo es sincrónico y su contador sigue contando sólo cuando el pulso es alto.

Su generador de pulsos puede operar a otras velocidades, por debajo de la velocidad principal del reloj, teniendo otro contador que emite un solo pulso de ancho de reloj cada vez que da una vuelta. Aumentando la cuenta de terminales de este contador, obtienes más tiempo entre pulsos, y movimientos más lentos de tu motor.

1voto

Alex Andronov Puntos 178

Hay varias formas de producir una frecuencia que sea un submúltiplo ajustable (posiblemente fraccionario) de un reloj de entrada. Un enfoque 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 el acumulador de fase. Cada ciclo de reloj, añade el valor de la frecuencia al acumulador de fase y emite un pulso si hay un acarreo. Si los valores son 16 bits y el valor de la frecuencia es 573, entonces habrá 573 pulsos de salida más o menos espaciados 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 particular va a cambiar de un cero a un uno (la salida controlada al LSB del contador pulsará en la mitad de los ciclos, la siguiente salida pulsará en 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 ninguno de los inferiores lo hace. Si uno une la salida del MSB del registro de "frecuencia deseada" con el LSB del circuito mencionado, y los bits restantes del registro de frecuencia en secuencia descendente con los 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 jitter que con el enfoque del acumulador de fase, pero la cantidad de circuitos necesarios para cada salida puede ser reducida. Tenga en cuenta también que los cambios en la frecuencia programada deben estar sincronizados con los tiempos de envolvimiento del temporizador, o de lo contrario pueden causar interrupciones en la salida.

Otro enfoque, que es una especie de híbrido entre los dos primeros, es tener un contador diseñado de manera que una determinada entrada lo haga "calar" durante un ciclo, y cablear un circuito similar al segundo anterior a ese circuito de calado. 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 avance, haciendo así que la tasa de envolvimiento del contador sea ajustable de 256 a 511. Si se utiliza un multiplexor de 8 vías, se puede tener una relación de división de K/2^N donde K es 256-511 y N es de cero a siete. Ten en cuenta que las "derivaciones" de orden inferior del contador tendrán mucho jitter, pero las de orden superior serán mucho más limpias.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X