Generieren von Impulsfolgen unterschiedlicher Frequenz auf einem FPGA

Ich arbeite daran, eine Impulsfolge zu erzeugen, um einen Motor zu steuern, der eine Impulsfolge als Eingang akzeptiert. Jeder Impuls entspricht einem voreingestellten Bewegungsinkrement; Ich kann einen Impuls gleich 1/1000 Grad (oder was auch immer) einstellen, und wenn ich ihm 20 Impulse sende, bewegt sich der Motor um 20/1000 Grad.

Die Software, die das ganze schwere Heben durchführt und bestimmt, wohin der Motor zu einem bestimmten Zeitpunkt kommandiert werden muss, wird in LabVIEW programmiert. Dieses Programm sendet dann Positions- und Geschwindigkeitsbefehle (als 32-Bit-Ganzzahlen) an ein FPGA, das ich verwenden möchte, um eine Reihe von Impulsen zu erzeugen, um dem Motor mitzuteilen, wie weit und wie schnell er sich bewegen soll. Ich habe einen einfachen Impulsgenerator, der nur die erforderliche Anzahl von Impulsen mit der Taktrate des FPGA ausgibt (siehe Diagramm unten). Wie kann ich die Geschwindigkeit dieser Impulse in meinem FPGA steuern?

Ich verwende ein in Quartus II v9.0 programmiertes Altera FPGA.

schematisch

Simulieren Sie diese Schaltung – Mit CircuitLab erstellter Schaltplan

Beachten Sie den invertierenden Anschluss für a = b?am Komparator. Das FPGA gibt dann die Werte von aus pulseund signsagt meinem Motor, wie weit er sich drehen soll und in welche Richtung. Eingaben an das FPGA sind die ganzzahlige Anzahl von Impulsen, die wir erzeugen möchten, ref[31..00], und ein boolesches Schreib-Flag, writeF. Mehrere Motoren werden von einem Programm gesteuert, daher muss angegeben werden, wann die Daten auf dem Bus ref[31..00]für einen bestimmten Motor gelten. Das höchstwertige Bit des Referenzwerts steuert die Bewegungsrichtung und err31wird somit als Eingang zum updownTerminal verwendet.

Wie Sie sehen können, zählt der Zähler die Anzahl der erzeugten Impulse pulseals Takteingang, wird aber pulsenur mit der Taktrate des FPGA generiert. Kann ich angesichts eines zusätzlichen Eingangs zu meinem FPGA zur Steuerung der Pulsfrequenz die Pulsfrequenz variabel machen?

BEARBEITEN: Ich habe meine Schaltung so geändert, dass der Systemtakt in den clockEingang meines Zählers eingeht und mein pulseAusgang als Taktaktivierungssignal ( clock_en) für diesen Zähler verwendet wird. Früher hatte ich meinen pulseAusgang direkt an meinen clockEingang angeschlossen, was möglicherweise schlecht ist. Ich werde meine Ergebnisse hier veröffentlichen, wenn ich Vorschläge umgesetzt habe.

Lösung für VHDL-Variablenzähler

Ich versuche, den Ansatz von David Kessner mit VHDL umzusetzen. Grundsätzlich erstelle ich einen Zähler, der um andere Zahlen als 1 erhöht werden kann, und verwende den Rollover dieses Zählers, um zu bestimmen, wann ich einen Impuls erzeugen soll. Der Code sieht bisher so aus:

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

Ich beabsichtige, das MSB entweder direkt auszugeben oder das MSB von diesem Zähler ( msb_o(k)) zu nehmen, es durch ein Einzelbit-DQ-Flipflop zu leiten, sodass ich auch habe msb_o(k-1), und jedes Mal, wenn mein Variablenzähler durch Ausführen überrollt, einen Impuls auszugeben :

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

wo ~bezeichnet logisch NOT, und *bezeichnet logisch AND. Dies ist das erste VHDL-Programm, das ich geschrieben habe, und ich habe es größtenteils mit this , this und this geschrieben . Hat jemand eine Empfehlung, wie ich meinen Code verbessern könnte? Leider bekomme ich immer noch keine Impulse aus meinem FPGA.

BEARBEITEN: VHDL-Code auf die aktuelle Implementierung aktualisiert (2013-08-12). Auch das Hinzufügen dieses kostenlosen Buches zur Liste der Referenzen.

BEARBEITEN 2: Mein Code wurde auf die (endgültige) Arbeitsversion aktualisiert.

Antworten (4)

Was Sie tun möchten, wird als numerisch gesteuerter "Oszillator" oder NCO bezeichnet. Es funktioniert so ...

Erstellen Sie einen Zähler, der um andere Werte als 1 inkrementieren kann. Die Eingaben für diesen Zähler sind die Hauptuhr und ein Wert, um den gezählt werden soll (din). Für jede Taktflanke count <= count + din. Die Anzahl der Bits in din ist die gleiche wie die Anzahl der Bits im Zähler. Der tatsächliche Zählwert kann für viele nützliche Dinge verwendet werden, aber was Sie tun möchten, ist super einfach.

Sie möchten jedes Mal erkennen, wenn der Zähler überrollt, und in diesem Fall einen Impuls an Ihren Motor ausgeben. Tun Sie dies, indem Sie das höchstwertige Bit des Zählers nehmen und es durch ein einzelnes Flip-Flop laufen lassen, um es um einen Takt zu verzögern. Jetzt haben Sie zwei Signale, die ich MSB nennen werde, und MSB_Previous. Sie wissen, ob der Zähler abgelaufen ist, weil MSB=0 und MSB_Prev=1. Wenn diese Bedingung wahr ist, senden Sie einen Impuls an den Motor.

Um die Pulsfrequenz einzustellen, lautet die Formel: pulse_rate = main_clk_freq * inc_value/2^n_bits

Dabei ist inc_value der Wert, um den der Zähler erhöht wird, und n_bits ist die Anzahl der Bits im Zähler.

Es ist wichtig zu beachten, dass das Hinzufügen von Bits zum Zähler den Bereich der Ausgangsfrequenz nicht ändert – das ist immer 0 Hz bis zur Hälfte von main_clk_freq. Aber es ändert die Genauigkeit, mit der Sie die gewünschte Frequenz erzeugen können. Die Chancen stehen gut, dass Sie für diesen Zähler keine 32 Bit benötigen und dass vielleicht nur 10 bis 16 Bit ausreichen.

Diese Methode zum Erzeugen von Impulsen ist gut, weil sie sehr einfach ist, die Logik klein und schnell ist und Frequenzen oft genauer und flexibler erzeugen kann als die Art von Zähler + Komparator-Design, das Sie in Ihrer Frage haben.

Der Grund für die kleinere Logik liegt nicht nur darin, dass Sie mit einem kleineren Zähler auskommen können, sondern Sie nicht die gesamte Ausgabe des Zählers vergleichen müssen. Sie brauchen nur das obere Bit. Außerdem erfordert der Vergleich zweier großer Zahlen in einem FPGA normalerweise viele LUTs. Der Vergleich zweier 32-Bit-Zahlen würde 21 4-Eingangs-LUTs und 3 Logikpegel erfordern, während das NCO-Design 1 LUT, 2 Flip-Flops und nur 1 Logikpegel erfordert. (Ich ignoriere den Zähler, da er im Grunde für beide Designs gleich ist.) Der NCO-Ansatz ist viel kleiner, viel schneller, viel einfacher und liefert bessere Ergebnisse.

Update: Ein alternativer Ansatz zur Herstellung des Überrolldetektors besteht darin, einfach das MSB des Zählers an den Motor zu senden. Wenn Sie dies tun, hat das Signal zum Motor immer ein 50/50-Tastverhältnis. Die Wahl des besten Ansatzes hängt davon ab, welche Art von Impuls Ihr Motor benötigt.

Update: Hier ist ein VHDL-Code-Snippet für den 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);
Das macht sehr viel Sinn und klingt definitiv einfacher als der 42-teilige Schraubendreher-Ansatz, den ich zu implementieren versucht habe. Ich werde sehen, ob ich das zum Laufen bekomme und hier posten, was ich finde. Danke! Und danke für die Detailgenauigkeit, die Sie in Ihrer Antwort gegeben haben.
Abgesehen von einer eingebauten Funktion, die in anderen Schritten als eins zählen kann, was ich glaube ich nicht in Quartus II v9.0 habe, wie würde ich vorgehen, um eine davon in FPGA-Code zu bauen? Ich bin nicht sehr vertraut mit dem tatsächlichen Zeilencode, der für die Arbeit mit FPGAs verwendet wird (VHDL oder was auch immer?).
@engineero, Sie müssen VHDL oder Verilog lernen. Andernfalls werden Sie immer frustriert sein, FPGAs mit Schaltplänen zu entwickeln. Das Schreiben des Codes, den ich oben skizziert habe, dauert buchstäblich 3 Minuten. 5, wenn Sie vorsichtig sind. Verwenden Sie für den schematischen Eintrag jedoch einen n-Bit-Addierer und ein n-Bit-D-Flip-Flop.
Danke! Ich habe einen VHDL-Code geschrieben, um dies zu tun (glaube ich), aber er scheint nicht automatisch zu überrollen. Wenn ich das MSB ausgebe, geht es einfach hoch und bleibt hoch. Ich habe einen Fall in den Code eingefügt (der unmittelbar nach dem Absenden aktualisiert wird), um ihn zu überschreiben, wenn die Zählung erreicht wird 2^N - inc, aber ich erhalte eine Art zufällige Frequenz- und Arbeitszyklus-Rechteckwellen, was für mich keinen Sinn ergibt. Alle weiteren Vorschläge würden sehr geschätzt!
@Engineero Ich habe keine Ahnung, warum Ihr Code nicht automatisch überrollt. Sie müssen tatsächlich Code speziell schreiben, damit er NICHT überrollt wird. Ich aktualisiere meine Antwort, um einen Beispielcode zu zeigen (sehen Sie, es ist super einfach!).
OK, ich habe jetzt gelernt, meine Komponenten in VHDL zu codieren, und richte eine Testbench ein, um zu überprüfen, ob sie alle einzeln (mit diesem Teil bereits fertig) und zusammen (noch daran arbeiten) korrekt funktionieren. Es sieht so aus, als hätten Ihre Vorschläge dazu geführt, dass das variable Gegenstück der Schaltung perfekt funktioniert. Vielen Dank für deine Hilfe!

Ich gehe davon aus, dass dies ungefähr das funktionale Verhalten ist, das Sie implementieren möchten:

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;

Wenn Sie keine sehr strengen Anforderungen an das Timing der Impulsuhr haben, würde ich eine einfache Zustandsmaschine empfehlen, um die Impulsfolge zu erzeugen, anstatt die Uhr zu steuern. Um die Frequenz des Takts während der Laufzeit zu ändern, müssten Sie eine konfigurierbare PLL verwenden, aber wenn die Pulstaktfrequenz gleich oder kleiner als der Systemtakt ist, können Sie diese einfach verwenden, um den Pulstakt zu erzeugen.

Ein Beispiel für einen solchen Zustandsautomaten könnte beispielsweise sein:

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;
Danke schön. Ich bin mit Zustandsautomaten auf FPGAs nicht vertraut, obwohl ich Zustandsautomaten in anderen Sprachen programmiert habe. Könnten Sie erläutern, wie ich eine Zustandsmaschine verwenden würde, um dies zu tun? Wären t_pulse und n_pulses für Ihren Code auch nur ganze Zahlen (Anzahl der Taktzyklen bzw. Anzahl der Impulse)?
Danke für die ausführliche Antwort. Das einzige Problem ist, dass ich in der Lage sein muss, die Impulsfrequenz im laufenden Betrieb anzupassen, was ich mit einem Zähler mit variablem Inkrement und der Ausgabe des MSB der Zählung tun kann. Ich schätze deine Hilfe sehr!

Sie haben Ihre Uhr torgesteuert, was eine sehr schlechte Sache ist, es sei denn, Sie wissen, was Sie tun. Das gleiche Taktsignal muss an alle Takteingänge Ihrer Schaltungen gehen.

Sie müssen Ihr Impulssignal als Taktfreigabe für Ihren Zähler verwenden - auf diese Weise ist alles synchron und Ihr Zähler zählt immer noch nur, wenn der Impuls hoch ist.

Ihr Impulsgenerator kann mit anderen Geschwindigkeiten unterhalb der Haupttaktgeschwindigkeit betrieben werden, indem ein weiterer Zähler vorhanden ist, der bei jedem Umlauf einen einzelnen taktbreiten Impuls ausgibt. Indem Sie die Endzahl dieses Zählers erhöhen, werden Sie zwischen den Impulsen länger und die Bewegungen Ihres Motors langsamer.

Ooo, das klingt nach etwas, das ich tun könnte. Und ich habe bereits den Impuls aktualisiert, den ich erzeuge, um zu meinem clock enablePin zu gehen, nachdem ich ein ähnliches Problem mit der Encoder-Zähllogik auf meiner Platine angegangen bin. Ich werde meinen Schaltplan entsprechend aktualisieren und versuchen, einen Zähler zu verwenden, um meine Pulsperiode zu steuern. Danke!

Es gibt eine Reihe von Möglichkeiten, eine Frequenz zu erzeugen, die ein einstellbarer (möglicherweise gebrochener) Teiler eines Eingangstakts ist. Ein beliebter Ansatz, um jeden Bruch von 1/2^N bis 1-(1/2^N) zuzulassen, besteht darin, ein programmierbares N-Bit-Register zur Auswahl der gewünschten Frequenz und ein N-Bit-Register zu haben, das als Phasenakkumulator bezeichnet wird. Addiere bei jedem Taktzyklus den Frequenzwert zum Phasenakkumulator und gib einen Impuls aus, wenn es einen Übertrag gibt. Wenn die Werte 16 Bit sind und der Frequenzwert 573 ist, dann gibt es 573 mehr oder weniger gleich beabstandete Ausgangsimpulse alle 65.536 Eingangsimpulse. Änderungen an der angeforderten Frequenz wirken sich "sanft" auf die Ausgabe aus.

dann hat der Ausgang jedes Mal, wenn der Zähler umbricht, die entsprechende Anzahl von Impulsen gesendet. Beachten Sie, dass bei Verwendung dieses Ansatzes der Ausgang mehr Jitter aufweist als bei dem Phasenakkumulator-Ansatz, aber die Menge an Schaltungen, die für jeden Ausgang erforderlich sind, reduziert werden kann. Beachten Sie auch, dass Änderungen an der programmierten Frequenz mit Zeiten synchronisiert werden sollten, zu denen der Timer umläuft, da sie sonst zu einer Unterbrechung der Ausgabe führen können.

Ein weiterer Ansatz, der eine Art Hybrid zwischen den ersten beiden darstellt, besteht darin, einen Zähler so zu gestalten, dass ein bestimmter Eingang ihn für einen Zyklus "anhält", und eine Schaltung ähnlich der zweiten oben mit dieser Anhalteschaltung zu verbinden . Wenn man einen 8-Bit-Zähler hat, bewirkt der in das Frequenzregister programmierte Wert, dass er alle 256 Mal, wenn der Zähler vorrückt, zwischen einem und 255 Mal anhält, wodurch bewirkt wird, dass die Umbruchrate des Zählers von 256 bis 511 einstellbar ist verwendet man einen 8-Wege-Multiplexer, kann man somit ein Teilungsverhältnis von K/2^N haben, wobei K 256-511 und N 0 bis 7 ist. Beachten Sie, dass "Abgriffe" niedrigerer Ordnung vom Zähler viel Jitter aufweisen, die höherwertigen jedoch viel sauberer sind.