01signal.com

Steuerung von acht Servomotoren mit Smart Zynq

Diese Webseite gehört zu einer Gruppe kleiner Projekte, die die Funktionen des Smart-Zynq-Boards untersuchen.

Einführung

Es gibt mehrere einfache und preisgünstige DC-Servomotoren auf dem Markt, zum Beispiel SG90, MG90S, MG995 und MG996R. Diese Motoren sind für Hobbyprojekte gedacht, insbesondere für den Bau einfacher Roboter. Solche Motoren werden über ein einziges PWM-Signal angesteuert.

Manche Motoren drehen sich nur über einen begrenzten Winkelbereich, typischerweise 180 Grad. Bei solchen Motoren steuert das PWM-Signal die Winkelposition des Motors. Andere Motoren (oft als „360-Grad-Motoren“ bezeichnet) können sich kontinuierlich drehen. Bei diesen Motoren steuert das PWM-Signal die Drehgeschwindigkeit und die Drehrichtung. Beachten Sie, dass ein Motormodell oft in zwei Varianten erhältlich ist: eine Variante mit begrenztem Drehwinkel und eine zweite Variante, die sich kontinuierlich drehen kann. Beim Kauf eines solchen Motors ist es wichtig, auf diesen Unterschied zu achten.

Dieses Tutorial zeigt, wie man bis zu acht Servomotoren mit dem Smart-Zynq-Board verbindet und diese Motoren mithilfe eines einfachen Linux-Befehls steuert. Außerdem wird demonstriert, wie man die seekbaren Datenströme (seekable streams) von Xillybus nutzt, um im FPGA eine Registerschnittstelle zu implementieren.

Die in diesem Tutorial gezeigte elektrische Verdrahtung ist nur für einen Motor ausgelegt, um die Beschreibungen und Fotos einfach zu halten. Weitere Motoren lassen sich ganz einfach hinzufügen, indem man weitere Kabel an dieselben Bauteile anschließt, wie hier gezeigt.

Die elektrische Schnittstelle des Motors

Der Anschluss von Servomotoren des betreffenden Typs besteht aus drei Kabeln:

Das PWM-Signal muss eine Periodendauer von 20 ms (50 Hz) haben. Die Dauer, in der der Impuls high ist, steuert die Position oder die Winkelgeschwindigkeit des Motors.

Laut den meisten Datenblättern sollte die Impulsdauer zwischen 1 ms und 2 ms liegen. Diese Angabe ist für manche Motoren falsch. Beispielsweise liegt der korrekte Bereich für den SG90 von Tower Pro (mit einem Drehwinkelbereich von 180 Grad) ungefähr zwischen 500 µs und 2450 µs. Dieser Bereich entspricht einer Drehung um 180 Grad. Mit einem Impuls von bis zu 2560 µs lässt sich der Motor etwas weiter drehen. Impulse, die länger sind, ignoriert dieser Motor.

Um also den Bereich der Impulsdauern herauszufinden, auf den der Motor reagiert, ist es besser, mit dem Motor zu experimentieren, als sich auf das Datenblatt zu verlassen. Da diese Motoren für Hobbyprojekte gedacht sind, sind ihre technischen Daten möglicherweise nicht exakt.

Elektrische Verbindungen

Zunächst benötigt der Servomotor eine eigene Spannungsversorgung, um den mechanischen Motor mit Energie zu versorgen. Die Spannung dieser Versorgung beträgt +5 V. Es wird nicht empfohlen, die Spannungsversorgung des Smart-Zynq-Boards zu verwenden, da Motoren häufig plötzliche Spannungsschwankungen verursachen. Das Smart-Zynq-Board kann sich unzuverlässig verhalten, wenn seine Versorgungsspannung instabil ist.

Das PWM-Signal auf dem orangen Kabel sollte bei High-Pegel ungefähr die gleiche Spannung haben wie die Spannungsversorgung. Mit anderen Worten: Die Spannung zwischen dem orangen Kabel und Masse sollte entweder 0 V oder ungefähr +5 V betragen.

Die Ausgangsspannung des Smart-Zynq-Boards beträgt im High-Zustand jedoch nur 3,3 V. Wenn ein solcher Ausgang direkt mit dem orangen Kabel des Motors verbunden wird, besteht eine gute Chance, dass der Motor korrekt reagiert. Der Grund ist, dass digitale Schaltungen mit einer 5-V-Versorgungsspannung normalerweise jede Spannung über 2,5 V als logische „1“ betrachten.

Allerdings ist ungewiss, ob der Motor mit einer Spannung von 3,3 V am PWM-Signal zuverlässig funktioniert. Die empfohlene Lösung ist die Verwendung eines Pegelwandlers (level shifter), der den Ausgang des Smart-Zynq-Boards in 0 V oder 5 V umsetzt. In diesem Tutorial wird eine kleine Platine mit dem Chip TXS0108E verwendet. Diese Platine führt lediglich die Pins des Chips heraus, sodass normale Kabel an den Chip angeschlossen werden können.

Dieses Bild zeigt, wie die elektrischen Verbindungen zur Steuerung eines Servomotors angeordnet sind: Das Smart-Zynq-Board am unteren Bildrand ist über Dupont-Kabel mit der TXS0108E-Platine verbunden. Die andere Seite dieser Platine ist sowohl mit dem PWM-Eingang des Servomotors als auch mit der separaten Spannungsversorgung für die Motoren verbunden (über einen Adapter für einen üblichen Netzstecker). Die roten und braunen Kabel des Motors sind ebenfalls auf diese Platine gelötet, um den Motor mit seiner Versorgungsspannung zu versorgen.

Connecting the Smart Zynq board with a servo motor with the help of a TXS0108E voltage level shifter

Dieses Bild zeigt die TXS0108E-Platine und ihre Verbindungen aus der Nähe:

Connections to the TXS0108E voltage level shifter, in detail

Achten Sie auf das rote Kabel zwischen OE und VA. Es stellt sicher, dass der Output-Enable (Ausgangsfreigabe) des Chips high ist.

Die Verbindungen mit der Stiftleiste des Smart Zynq sind im folgenden Bild dargestellt. Der Motor wird bei dieser Verdrahtung über J6/1 angesteuert.

Dupont wire connections with the Smart Zynq board for controlling a servo motor

Diese Tabelle fasst die Verbindungen zwischen den vier beteiligten Teilen zusammen:

Pin der TXS0108E-Platine Pin des Smart Zynq Kabel des Motors Spannungsversorgung +5 V Kommentar
VA J6/37 (3,3 V) -- -- Außerdem mit OE verbunden
A4 J6/1 -- --
OE -- -- -- Mit VA verbunden
GND J6/35 (GND) Braunes Kabel GND
VB -- Rotes Kabel +5 V
B4 -- Oranges Kabel --

Der Chip TXS0108E führt eine Pegelumsetzung von seinem Eingang A4 zu seinem Ausgang B4 durch. Auf dem Chip gibt es sieben weitere Pinpaare. Das Paar A4/B4 wurde für dieses Tutorial willkürlich gewählt. Um acht Motoren anzusteuern, können alle acht Pinpaare dieses Chips verwendet werden. Das wird hier nicht gezeigt, weil die Anzahl der Kabel zur Steuerung von acht Motoren die Bilder unübersichtlich machen würde.

Um die Stiftleiste J6 zu finden, suchen Sie auf der Rückseite des Smart-Zynq-Boards nach der Aufschrift „Bank 33 VCCIO Vadj“. Die Pinreihe nahe dieser Markierung ist die Stiftleiste, die in diesem Tutorial verwendet wird. J6/1 ist also der Pin, der am nächsten zum HDMI-Stecker liegt.

Beachten Sie, dass das Smart-Zynq-Board über einen seiner USB-Anschlüsse an eine separate Spannungsversorgung angeschlossen ist. Beachten Sie außerdem, dass der letzte Pin der Stiftleiste J6 5 V führt. Schließen Sie also kein Kabel an diesen Pin an.

Vorbereitung des Vivado-Projekts

Erstellen Sie ein neues Vivado-Projekt aus der Zip-Datei des Demopakets (demo bundle), also des Boot-Partition-Kits. Öffnen Sie verilog/src/xillydemo.v in einem Texteditor. Löschen Sie den Teil des Codes, der mit „PART 4“ gekennzeichnet ist. Fügen Sie anstelle dieses Teils Folgendes ein:

   assign  user_r_mem_8_empty = 0;
   assign  user_r_mem_8_eof = 0;
   assign  user_w_mem_8_full = 0;

   reg [7:0] reg0, reg1, reg2, reg3, reg4, reg5, reg6, reg7;

   always @(posedge bus_clk)
     if (user_w_mem_8_wren)
       case (user_mem_8_addr[2:0])
	 0: reg0 <= user_w_mem_8_data;
	 1: reg1 <= user_w_mem_8_data;
	 2: reg2 <= user_w_mem_8_data;
	 3: reg3 <= user_w_mem_8_data;
	 4: reg4 <= user_w_mem_8_data;
	 5: reg5 <= user_w_mem_8_data;
	 6: reg6 <= user_w_mem_8_data;
	 7: reg7 <= user_w_mem_8_data;
       endcase

   reg [7:0] mem_8_data_reg;
   assign user_r_mem_8_data = mem_8_data_reg;

   always @(posedge bus_clk)
     if (user_r_mem_8_rden)
       case (user_mem_8_addr[2:0])
	 0: mem_8_data_reg <= reg0;
	 1: mem_8_data_reg <= reg1;
	 2: mem_8_data_reg <= reg2;
	 3: mem_8_data_reg <= reg3;
	 4: mem_8_data_reg <= reg4;
	 5: mem_8_data_reg <= reg5;
	 6: mem_8_data_reg <= reg6;
	 7: mem_8_data_reg <= reg7;
       endcase

   servo_pwm servo_pwm_i [7:0]
     (
      .clk(bus_clk),
      .rst(quiesce),
      .pwm_signal(J6[7:0]),
      .pwm_width( { reg7, reg6, reg5, reg4, reg3, reg2, reg1, reg0 } )
      );

Die aktualisierte xillydemo.v kann auch von diesem Link heruntergeladen werden.

Erstellen Sie eine neue Datei servo_pwm.v im Verzeichnis verilog/src/ des Projekts mit folgendem Inhalt (per Kopieren und Einfügen oder durch Herunterladen von diesem Link).

module servo_pwm
  (
   input clk,
   input rst,

   output reg pwm_signal,
   input [7:0] pwm_width
  );

   reg [9:0]  enable_count;
   reg 	      enable;

   reg [10:0] pwm_count;
   reg [8:0]  threshold;

   always @(posedge clk)
     begin
	if (enable_count == 999)
	  begin
	     enable_count <= 0;
	     enable <= 1;
	  end
	else
	  begin
	     enable_count <= enable_count + 1;
	     enable <= 0;
	  end

	threshold <= pwm_width + 50; // Add 0.5 ms to PWM width

	if (enable)
	  begin
	     if (pwm_count == 1999)
	       pwm_count <= 0;
	     else
	       pwm_count <= pwm_count + 1;

	     pwm_signal <= (pwm_count < threshold);
	  end

	if (rst)
	  begin
	     enable_count <= 0;
	     pwm_count <= 0;
	     pwm_signal <= 0;
	  end
     end
endmodule

Fügen Sie die Datei dann zum Vivado-Projekt hinzu: Klicken Sie auf File > Add Sources… und wählen Sie „Add or create design sources“. Klicken Sie dann auf Next. Klicken Sie auf die Schaltfläche „Add Files“ und wählen Sie die Datei „servo_pwm.v“ im Verzeichnis verilog/src/. Klicken Sie anschließend auf „Finish“.

Erzeugen Sie aus dem aktualisierten Projekt eine Bitstromdatei (bitstream), und zwar auf dieselbe Weise, wie Sie die Bitstromdatei für das Demopaket (demo bundle) erzeugt haben. Kopieren Sie die Bitstromdatei außerdem auf dieselbe Weise auf die TF-Karte. Überschreiben Sie dabei die alte Datei xillydemo.bit mit der in diesem Projekt erzeugten Datei.

Den Servomotor ansteuern

Die in diesem Abschnitt gezeigten Schritte sind an einer Shell-Eingabeaufforderung auf dem Smart-Zynq-Board auszuführen.

Wechseln Sie zuerst das Verzeichnis und kompilieren Sie die Demo-Anwendungen für Xillybus:

# cd ~/xillybus/demoapps/
# make
gcc  -g -Wall -O3 memwrite.c -o memwrite
gcc  -g -Wall -O3 memread.c -o memread
gcc  -g -Wall -O3 streamread.c -o streamread
gcc  -g -Wall -O3 streamwrite.c -o streamwrite
gcc  -g -Wall -O3 -pthread fifo.c -o fifo

Im FPGA gibt es acht Register zur Steuerung des Servomotors. Anfänglich sind ihre Werte alle null. So geben Sie diese Werte im Dezimalformat aus:

# hexdump -v -n 8 -e '8/1 "%u " "\n" ' /dev/xillybus_mem_8
0 0 0 0 0 0 0 0

Mit einem einfacheren Hexdump-Befehl lassen sich dieselben Werte im Hexadezimalformat ausgeben:

# hexdump -v -n 8 -C /dev/xillybus_mem_8
00000000  00 00 00 00 00 00 00 00                           |........|
00000008

Das Programm memwrite (im Verzeichnis xillybus/demoapps/) kann zum Steuern des Motors verwendet werden. Beispielsweise ändert dieser Befehl den Wert in Register 0 auf 120:

# ./memwrite /dev/xillybus_mem_8 0 120

Dadurch wird die Drehposition oder Geschwindigkeit des Motors geändert, der mit J6/1 verbunden ist (also wie in den obigen Fotos gezeigt).

Die Breite des PWM-Impulses hängt gemäß dieser Formel vom Wert des Registers ab:

t = 500 + (x * 10)

In dieser Formel ist t in Mikrosekunden angegeben, und x ist der Wert des zugehörigen Registers. Die Standardbreite des Impulses beträgt also 500 µs (d. h. für x=0). Der obige Befehl hat den Wert des Registers auf 120 geändert. Dadurch hat sich die Impulsbreite auf 1700 µs geändert.

Jedes Register besteht aus einem Byte, sein Wert reicht also von 0 bis 255. Dementsprechend kann der Impuls für jeden Motor auf eine Breite zwischen 500 µs und 3050 µs eingestellt werden. Beachten Sie, dass ein Teil dieses Wertebereichs von den meisten Motoren als unzulässig angesehen wird. Am besten probiert man mit jedem Motor verschiedene Einstellungen aus und beobachtet, wie er reagiert.

Um einen Motor anzusteuern, der mit einem anderen Pin verbunden ist, schreiben Sie in ein anderes Register. Wenn der Motor beispielsweise mit J6/4 des Smart Zynq verbunden ist, kann dieser Befehl verwendet werden:

# ./memwrite /dev/xillybus_mem_8 3 50

Dieser Befehl ändert die Impulsbreite auf 1000 µs (also 1 ms).

Nach diesen beiden Befehlen lassen sich die Änderungen durch erneutes Lesen der Werte überprüfen:

# hexdump -v -n 8 -e '8/1 "%u " "\n" ' /dev/xillybus_mem_8
120 0 0 50 0 0 0 0

Dies zeigt die Werte im Dezimalformat. Für das Hexadezimalformat:

# hexdump -v -n 8 -C /dev/xillybus_mem_8
00000000  78 00 00 32 00 00 00 00                           |x..2....|
00000008

Wie memwrite funktioniert

Der Quellcode von memwrite befindet sich in der Datei memwrite.c im Verzeichnis ~/xillybus/demoapps/. Dieses Programm zeigt, wie man mit Hilfe der seekbaren Datenströme von Xillybus auf Register zugreift. Wenn Sie verstehen möchten, wie dieses Programm funktioniert, schauen Sie sich am besten den Quellcode an. Ich werde im Folgenden nur die zwei wichtigen Teile durchgehen.

Das Programm öffnet die Datei, deren Name im ersten Argument angegeben ist. Der Dateideskriptor für diese Datei wird in der Variablen @fd gespeichert.

Anschließend führt das Programm einen Funktionsaufruf an lseek() folgendermaßen aus:

  if (lseek(fd, address, SEEK_SET) < 0) {
    perror("Failed to seek");
    exit(1);
  }

Die Variable @address enthält das zweite Argument des Programms. Im ersten Beispiel zur Verwendung von memwrite oben war das 0, im zweiten Beispiel 3. Im Allgemeinen wird lseek() verwendet, um an eine bestimmte Position innerhalb einer Datei zu springen. In diesem Fall entspricht die Position in der Datei der Nummer des Registers, auf das wir zugreifen möchten.

Als Nächstes ruft das Programm die Funktion allwrite() auf:

  allwrite(fd, &data, 1);

Damit wird ein Byte in die Datei geschrieben. allwrite() ist in memwrite.c definiert und ähnelt der bekannten Funktion write(). Anstelle von allwrite() wäre also Folgendes fast dasselbe:

  write(fd, &data, 1);

Der Unterschied besteht darin, dass write() nicht sicherstellt, dass die Daten geschrieben werden. Die Funktion allwrite() garantiert dagegen, dass die Daten in die Datei geschrieben werden.

In diesem Fall schreibt der Funktionsaufruf von allwrite() den Wert des dritten Arguments des Programms. Mit anderen Worten: Das ist der neue Wert des Registers.

Weitere Einzelheiten über die Xillybus-API auf Host-Seite finden Sie in der Dokumentation zu diesem Thema, insbesondere in Abschnitt 6.1.

Erläuterung des Verilog-Codes

Ich zeige zunächst, wie der Verilog-Code in xillydemo.v die Register im FPGA implementiert. Danach folgt die Implementierung des PWM-Impulses.

Die API von Xillybus zur Implementierung von Hardware-Registern wird im Xillybus-FPGA-Designerhandbuch ausführlich erklärt.

Die zu den Registern gehörenden Leitungen waren in der ursprünglichen Datei xillydemo.v bereits definiert:

  // Wires related to /dev/xillybus_mem_8
  wire  user_r_mem_8_rden;
  wire  user_r_mem_8_empty;
  wire [7:0] user_r_mem_8_data;
  wire  user_r_mem_8_eof;
  wire  user_r_mem_8_open;
  wire  user_w_mem_8_wren;
  wire  user_w_mem_8_full;
  wire [7:0] user_w_mem_8_data;
  wire  user_w_mem_8_open;
  wire [4:0] user_mem_8_addr;
  wire  user_mem_8_addr_update;

Diese Leitungen sind im Rahmen seiner Instanziierung (instantiation) mit dem Xillybus-IP-Kern (IP core) verbunden:

  xillybus xillybus_ins (

    // Ports related to /dev/xillybus_mem_8
    // FPGA to CPU signals:
    .user_r_mem_8_rden(user_r_mem_8_rden),
    .user_r_mem_8_empty(user_r_mem_8_empty),
    .user_r_mem_8_data(user_r_mem_8_data),
    .user_r_mem_8_eof(user_r_mem_8_eof),
    .user_r_mem_8_open(user_r_mem_8_open),

    // CPU to FPGA signals:
    .user_w_mem_8_wren(user_w_mem_8_wren),
    .user_w_mem_8_full(user_w_mem_8_full),
    .user_w_mem_8_data(user_w_mem_8_data),
    .user_w_mem_8_open(user_w_mem_8_open),

    // Address signals:
    .user_mem_8_addr(user_mem_8_addr),
    .user_mem_8_addr_update(user_mem_8_addr_update),

[ ... ]

    .quiesce(quiesce)
  );

Als Nächstes betrachten wir den Verilog-Code, der speziell für dieses Projekt hinzugefügt wurde. Er beginnt mit diesem Teil, der sicherstellt, dass beim Datenaustausch mit dem Host keine Flusskontrolle (flow control) angewendet wird:

   assign  user_r_mem_8_empty = 0;
   assign  user_r_mem_8_eof = 0;
   assign  user_w_mem_8_full = 0;

Die acht Register werden als Nächstes deklariert:

   reg [7:0] reg0, reg1, reg2, reg3, reg4, reg5, reg6, reg7;

Der nächste Teil implementiert Schreibvorgänge auf die Register: Wenn @user_w_mem_8_wren high ist, wird ein neuer Wert in eines der Register geschrieben. @user_mem_8_addr wählt aus, welches Register betroffen ist. Sowohl @user_w_mem_8_wren als auch @user_mem_8_addr sind Ausgänge des Xillybus-IP-Kerns.

   always @(posedge bus_clk)
     if (user_w_mem_8_wren)
       case (user_mem_8_addr[2:0])
	 0: reg0 <= user_w_mem_8_data;
	 1: reg1 <= user_w_mem_8_data;
	 2: reg2 <= user_w_mem_8_data;
	 3: reg3 <= user_w_mem_8_data;
	 4: reg4 <= user_w_mem_8_data;
	 5: reg5 <= user_w_mem_8_data;
	 6: reg6 <= user_w_mem_8_data;
	 7: reg7 <= user_w_mem_8_data;
       endcase

Danach wird das Lesen der Werte aus diesen Registern implementiert: Wenn @user_r_mem_8_rden high ist, wird @user_r_mem_8_data aktualisiert, sodass es den Wert eines der Register enthält. @user_mem_8_addr wählt aus, welcher Registerwert gelesen wird.

   reg [7:0] mem_8_data_reg;
   assign user_r_mem_8_data = mem_8_data_reg;

   always @(posedge bus_clk)
     if (user_r_mem_8_rden)
       case (user_mem_8_addr[2:0])
	 0: mem_8_data_reg <= reg0;
	 1: mem_8_data_reg <= reg1;
	 2: mem_8_data_reg <= reg2;
	 3: mem_8_data_reg <= reg3;
	 4: mem_8_data_reg <= reg4;
	 5: mem_8_data_reg <= reg5;
	 6: mem_8_data_reg <= reg6;
	 7: mem_8_data_reg <= reg7;
       endcase

Schließlich erfolgt die Instanziierung des Moduls servo_pwm.

   servo_pwm servo_pwm_i [7:0]
     (
      .clk(bus_clk),
      .rst(quiesce),
      .pwm_signal(J6[7:0]),
      .pwm_width( { reg7, reg6, reg5, reg4, reg3, reg2, reg1, reg0 } )
      );

Beachten Sie, dass diese Instanziierung acht identische Kopien von servo_pwm erzeugt: Die erste Kopie ist mit reg0 und J6[0] verbunden, die zweite Kopie mit reg1 und J6[1] usw.

Wir sehen uns nun das Modul servo_pwm an. Es beginnt mit der Deklaration der Ports und Register des Moduls:

module servo_pwm
  (
   input clk,
   input rst,

   output reg pwm_signal,
   input [7:0] pwm_width
  );

   reg [9:0]  enable_count;
   reg 	      enable;

   reg [10:0] pwm_count;
   reg [8:0]  threshold;

Zur Erinnerung: @pwm_width erhält den Wert des Registers, der mithilfe des Befehls memwrite auf dem Host aktualisiert wird. @pwm_signal ist mit dem Pin des Smart-Zynq-Boards verbunden. Das ist also das Signal, das über das orange Kabel am Motor ankommt.

Der nächste Teil dieses Moduls implementiert ein Strobe-Signal: @enable ist einmal in jeweils 1000 Taktzyklen high. Die Frequenz von @clk beträgt 100 MHz. Daher ist @enable einmal in jedem Zeitabschnitt von 10 µs high.

   always @(posedge clk)
     begin
	if (enable_count == 999)
	  begin
	     enable_count <= 0;
	     enable <= 1;
	  end
	else
	  begin
	     enable_count <= enable_count + 1;
	     enable <= 0;
	  end

Wie bereits erwähnt, entspricht der Registerwert von 0 bis 255 einer PWM-Impulsbreite zwischen 500 µs und 3050 µs (der Ausdruck für die Impulsbreite in Mikrosekunden war 500 + x * 10).

Daher lautet der Ausdruck für @threshold folgendermaßen:

	threshold <= pwm_width + 50; // Add 0.5 ms to PWM width

Dieses Register enthält die Anzahl der Male, die @enable während des PWM-Impulses (also wenn das PWM-Signal high ist) high ist. Dieser Ausdruck trägt der Tatsache Rechnung, dass der Registerwert die Länge des PWM-Impulses in Einheiten von 10 µs angibt. Die Addition von 50 zum Registerwert entspricht der minimalen Impulsbreite von 500 µs.

@enable wird nun als Taktfreigabe (clock enable) für diesen Teil verwendet:

	if (enable)
	  begin
	     if (pwm_count == 1999)
	       pwm_count <= 0;
	     else
	       pwm_count <= pwm_count + 1;

	     pwm_signal <= (pwm_count < threshold);
	  end

@pwm_count zählt von 0 bis 1999 und beginnt dann wieder von vorn. Dieser Zähler ändert sich nur einmal alle 10 µs, durchläuft also alle 20000 µs = 20 ms einen vollständigen Zyklus. Mit anderen Worten: Die Wiederholungsperiode des PWM-Impulses beträgt wie gefordert 20 ms.

@pwm_signal ist high, solange @pwm_count kleiner als @threshold ist. Auf diese Weise wird die Impulsbreite durch das Register gesteuert.

Der letzte Teil dieses Moduls setzt einige Register zurück:

	if (rst)
	  begin
	     enable_count <= 0;
	     pwm_count <= 0;
	     pwm_signal <= 0;
	  end
     end
endmodule

Wenn @rst high ist, hat dieser Teil Vorrang vor allem, was oben geschrieben wurde. @rst übernimmt also die Funktion eines Resetsignals.

Der Zusammenhang zwischen Verilog-Code und echten Pins

Der obige Verilog-Code verwendet den Inout-Port mit dem Namen J6. Aber wie gelangen die Verbindungen dieses Ports zur Stiftleiste? Die Antwort findet sich in xillydemo.xdc. Diese Datei gehört zu dem Vivado-Projekt, das den Bitstrom (bitstream) erzeugt (im Verzeichnis „vivado-essentials“).

xillydemo.xdc enthält verschiedene Informationen, die erforderlich sind, damit das FPGA als elektronisches Bauelement korrekt funktioniert. Unter anderem enthält diese Datei folgende Zeilen:

[ ... ]

## J6 on board (BANK33 VADJ)
set_property PACKAGE_PIN U22  [get_ports {J6[0]}];   #J6/1  = IO_B33_LN2
set_property PACKAGE_PIN T22  [get_ports {J6[1]}];   #J6/2  = IO_B33_LP2
set_property PACKAGE_PIN W22  [get_ports {J6[2]}];   #J6/3  = IO_B33_LN3
set_property PACKAGE_PIN V22  [get_ports {J6[3]}];   #J6/4  = IO_B33_LP3
set_property PACKAGE_PIN Y21  [get_ports {J6[4]}];   #J6/5  = IO_B33_LN9
set_property PACKAGE_PIN Y20  [get_ports {J6[5]}];   #J6/6  = IO_B33_LP9
set_property PACKAGE_PIN AB22 [get_ports {J6[6]}];   #J6/7  = IO_B33_LN7
set_property PACKAGE_PIN AA22 [get_ports {J6[7]}];   #J6/8  = IO_B33_LP7

[ ... ]

Die erste Zeile besagt, dass der Port J6[0] mit U22 verbunden werden soll. Dabei handelt es sich um eine Position auf dem physischen Gehäuse des FPGA. Laut den Schaltplänen des Smart Zynq ist dieser FPGA-Pin mit dem ersten Pin der Stiftleiste verbunden. Die Positionen der übrigen Ports sind auf dieselbe Weise festgelegt.

Zusammenfassung

Dieses Tutorial hat gezeigt, wie man Servomotoren mithilfe des Smart-Zynq-Boards und der Register-API von Xillybus ansteuert. Sowohl die Host-Anwendung als auch der zugehörige Verilog-Code wurden vorgestellt.

Dieses Tutorial kann auch als Grundlage für andere Anwendungen dienen, bei denen Hardware mithilfe von Registern gesteuert werden muss.

Diese Seite wurde maschinell aus dem Englischen übersetzt. Im Zweifelsfall siehe den Originaltext.
Copyright © 2021-2026. All rights reserved. (dcc38493)