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.
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 pulse
und sign
sagt 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 err31
wird somit als Eingang zum updown
Terminal verwendet.
Wie Sie sehen können, zählt der Zähler die Anzahl der erzeugten Impulse pulse
als Takteingang, wird aber pulse
nur 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 clock
Eingang meines Zählers eingeht und mein pulse
Ausgang als Taktaktivierungssignal ( clock_en
) für diesen Zähler verwendet wird. Früher hatte ich meinen pulse
Ausgang direkt an meinen clock
Eingang angeschlossen, was möglicherweise schlecht ist. Ich werde meine Ergebnisse hier veröffentlichen, wenn ich Vorschläge umgesetzt habe.
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.
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);
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;
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.
clock enable
Pin 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.
Ingenieur
Ingenieur
Benutzer3624
Ingenieur
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!Benutzer3624
Ingenieur