Industrielle Fertigung
Industrielles Internet der Dinge | Industrielle Materialien | Gerätewartung und Reparatur | Industrielle Programmierung |
home  MfgRobots >> Industrielle Fertigung >  >> Industrial programming >> VHDL

FPGA-basierten RC-Servo-Controller mit präzisem PWM entwerfen

Ferngesteuerte (RC) Servos sind das Herzstück von Hobby-Flugzeugen, -Autos und -Booten. Sie übersetzen einen Fernfunkbefehl in eine präzise Winkelposition. Da RC-Systeme seit Jahrzehnten existieren, ist der Industriestandard Pulsweitenmodulation (PWM) statt eines rein digitalen Protokolls. Mit dem deterministischen Timing, das ein Field-Programmable Gate Array (FPGA) bietet, ist die Implementierung hochpräziser PWM unkompliziert. Dieser Artikel führt durch einen generischen VHDL-Servo-Controller, der mit jedem RC-Servo per PWM funktioniert, inklusive Simulations-Testbench und Realwelt-Implementierung auf einem Lattice iCEstick FPGA-Board.

Wie PWM-Steuerung für einen RC-Servo funktioniert

Während PWM-Grundlagen anderswo behandelt werden, erfordern RC-Servos einen leicht anderen Ansatz. Der Servo ignoriert den vollen Duty Cycle; er wertet nur die Dauer der High-Phase jedes Pulses aus. Das Standardintervall zwischen Pulsen beträgt 20 ms, was einer Frequenz von 50 Hz entspricht. Innerhalb dieses 20-ms-Fensters liest der Servo einen Puls, dessen Breite typischerweise von 1 ms (Minimalposition) bis 2 ms (Maximalposition) reicht, wobei die genauen Werte je nach Hersteller variieren.

FPGA-basierten RC-Servo-Controller mit präzisem PWM entwerfen

Die Abbildung veranschaulicht die Timing-Beziehung: Eine 20-ms-Periode wiederholt sich, und die High-Time jedes Pulses bildet direkt die Winkelposition des Servos ab.

Der VHDL-Servo-Controller

Wir bauen ein generisches VHDL-Modul, das Generics für Taktfrequenz, PWM-Pulsfrequenz, Minimal- und Maximalpulsbreite (in Mikrosekunden) und die Anzahl der diskreten Positionen zwischen den Extremen akzeptiert. Da der FPGA-Takt im Megahertz-Bereich läuft, liefert Integer-Zählerarithmetik ausreichende Auflösung. Um Rundungsmehrdeutigkeiten zu vermeiden, nutzen wir die round-Funktion aus dem ieee.math_real-Paket, die immer von Null weg rundet.

library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; use ieee.math_real.round;

Im Folgenden die Entity-Deklaration für das Servo-Modul:

entity servo is generic ( clk_hz : real; pulse_hz : real; -- PWM-Pulsfrequenz min_pulse_us : real; -- Mikrosekunden bei Minimalposition max_pulse_us : real; -- Mikrosekunden bei Maximalposition step_count : positive -- Anzahl diskreter Positionen ); port ( clk : in std_logic; rst : in std_logic; position : in integer range 0 to step_count - 1; pwm : out std_logic ); end servo;

Der position-Eingang steuert den Servo von Minimum (0) bis Maximum (step_count-1). Der pwm-Ausgang verbindet sich mit dem Signal-Draht des Servos (meist gelb oder weiß). Die meisten FPGAs geben 3,3-V-Logik aus, daher ist ein Level-Shifter erforderlich, um den 5-V-Eingang des Servos anzupassen.

Der deklarative Bereich

Wir beginnen mit einer Hilfsfunktion, die eine Zeit in Mikrosekunden in die nächstgelegene ganzzahlige Anzahl Takzyklen umrechnet:

function cycles_per_us (us_count : real) return integer is begin return integer(round(clk_hz / 1.0e6 * us_count)); end function;

Mit dieser Funktion berechnen wir die Kern-Timing-Konstanten:

constant min_count : integer := cycles_per_us(min_pulse_us); constant max_count : integer := cycles_per_us(max_pulse_us); constant min_max_range_us : real := max_pulse_us - min_pulse_us; constant step_us : real := min_max_range_us / real(step_count - 1); constant cycles_per_step : positive := cycles_per_us(step_us);

Als Nächstes richten wir den PWM-Zähler ein, der mit der gewünschten Pulsfrequenz läuft:

constant counter_max : integer := integer(round(clk_hz / pulse_hz)) - 1; signal counter : integer range 0 to counter_max; signal duty_cycle : integer range 0 to max_count;

Takzyklen zählen

Der folgende Prozess implementiert den freilaufenden Zähler:

COUNTER_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then counter <= 0; else if counter < counter_max then counter <= counter + 1; else counter <= 0; end if; end if; end if; end process;

PWM-Ausgabe-Prozess

Der PWM-Ausgang wird für so viele Zählerzyklen high getrieben, wie der aktuelle duty_cycle-Wert vorgibt:

PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm <= '0'; else pwm <= '0'; if counter < duty_cycle then pwm <= '1'; end if; end if; end if; end process;

Duty Cycle berechnen

Bei Reset nimmt duty_cycle standardmäßig die Minimalpulsbreite an. Ansonsten wird er aus dem position-Eingang berechnet:

DUTY_CYCLE_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then duty_cycle <= min_count; else duty_cycle <= position * cycles_per_step + min_count; end if; end if; end process;

Obwohl cycles_per_step auf das nächste Integer gerundet wird, ist der resultierende Fehler vernachlässigbar, da der FPGA-Takt um Größenordnungen schneller ist als die 50-Hz-Pulsrate.

Der Servo-Testbench

Zur Verifikation des Designs haben wir einen einfachen Testbench gebaut, der manuell durch alle Positions-Werte schreitet und das Wellenform aufzeichnet. Ein 1-MHz-Testtakt beschleunigt die Simulation bei realistischem Timing.

Simulationskonstanten

constant clk_hz : real := 1.0e6; constant clk_period : time := 1 sec / clk_hz; constant pulse_hz : real := 50.0; constant pulse_period : time := 1 sec / pulse_hz; constant min_pulse_us : real := 1000.0; constant max_pulse_us : real := 2000.0; constant step_count : positive := 5;

DUT-Signale

signal clk : std_logic := '1'; signal rst : std_logic := '1'; signal position : integer range 0 to step_count - 1; signal pwm : std_logic;

Takterzeugung:

clk <= not clk after clk_period / 2;

DUT-Instanziierung

DUT : entity work.servo(rtl) generic map ( clk_hz => clk_hz, pulse_hz => pulse_hz, min_pulse_us => min_pulse_us, max_pulse_us => max_pulse_us, step_count => step_count ) port map ( clk => clk, rst => rst, position => position, pwm => pwm );

Testbench-Sequencer

Der Sequencer setzt das DUT zurück, iteriert dann durch alle Positions-Werte und wartet eine Pulperiode zwischen den Änderungen. Nach Abschluss der Sequenz endet die Simulation.

SEQUENCER : process begin wait for 10 * clk_period; rst <= '0'; wait for pulse_period; for i in 0 to step_count - 1 loop position <= i; wait for pulse_period; end loop; report "Simulation done. Check waveform."; finish; end process;

Servo-Simulations-Wellenform

Der folgende Snapshot zeigt die Testbench-Wellenform. Der PWM-Ausgang ist nur für die frühesten Zählerzyklen high, passend zur erwarteten Pulsbreite für jeden Positions-Wert.

FPGA-basierten RC-Servo-Controller mit präzisem PWM entwerfen

Laden Sie die vollständigen Projektdateien herunter, um die Simulation nachzuvollziehen.

Beispiel-FPGA-Implementierung

Als Nächstes steuern wir einen echten TowerPro SG90 Servo von einem Lattice iCEstick Board. Der Servo benötigt 5-V-Logikpegel, daher nutzen wir einen Level-Shifter zwischen FPGA-Pin und Servo-Signaldraht.

Top-Modul Entity

entity top is port ( clk : in std_logic; rst_n : in std_logic; -- Pull-up pwm : out std_logic ); end top;

Signale und Konstanten

Wir haben die Servo-Pulsbreiten experimentell kalibriert: 0,5 ms bis 2,5 ms ergeben einen vollen 180°-Sweep. Die folgenden Konstanten passen zum iCEstick-Takt und den Servo-Parametern.

constant clk_hz : real := 12.0e6; -- iCEstick-Takt constant pulse_hz : real := 50.0; constant min_pulse_us : real := 500.0; -- 0,5 ms constant max_pulse_us : real := 2500.0; -- 2,5 ms constant step_bits : positive := 8; -- 0 bis 255 constant step_count : positive := 2**step_bits;

Der freilaufende Zähler ist 25 Bit breit, was bei 12 MHz Takt eine Wrap-Zeit von ca. 2,8 Sekunden ergibt.

constant cnt_bits : integer := 25; signal cnt : unsigned(cnt_bits - 1 downto 0);

Module verbinden:

signal rst : std_logic; signal position : integer range 0 to step_count - 1; signal rom_addr : unsigned(step_bits - 1 downto 0); signal rom_data : unsigned(step_bits - 1 downto 0);

Servo-Modul-Instanziierung

SERVO : entity work.servo(rtl) generic map ( clk_hz => clk_hz, pulse_hz => pulse_hz, min_pulse_us => min_pulse_us, max_pulse_us => max_pulse_us, step_count => step_count ) port map ( clk => clk, rst => rst, position => position, pwm => pwm );

Selbst-Wrap-Zähler-Instanziierung

COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt_bits ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );

Sine-ROM-Instanziierung

SINE_ROM : entity work.sine_rom(rtl) generic map ( data_bits => step_bits, addr_bits => step_bits ) port map ( clk => clk, addr => rom_addr, data => rom_data );

Concurrent Assignments verdrahten Zähler, ROM und Servo zusammen:

position <= to_integer(rom_data); rom_addr <= cnt(cnt'left downto cnt'left - step_bits + 1);

Test auf dem Lattice iCEstick

Die gesamte Schaltung ist auf einem Breadboard aufgebaut: Der 3,3-V-Ausgang des FPGA wird für den Servo auf 5 V gelevel-shiftet. Das PWM-Signal verbindet sich mit Pin 119 am iCEstick. Bei Stromversorgung oszilliert der Servo sanft zwischen 0° und 180°, mit kurzer Pause an den Extremen. Ein Video des PWM-Wellenforms am Oszilloskop demonstriert das korrekte Timing.

FPGA-basierten RC-Servo-Controller mit präzisem PWM entwerfen

Abschließende Gedanken

Es gibt viele Wege, einen PWM-Servo-Controller in VHDL zu implementieren, aber die Nutzung von Integer-Zählern hält die synthetisierte Logik simpel – nur Zähler, Register und Multiplexer. Die häufigste Falle ist stiller Überlauf in 32-Bit-Arithmetik; prüfen Sie daher stets, dass Zwischenergebnisse für Ihre gewählten Parameter im Bereich bleiben.

Beachten Sie, dass dieser PWM-Stil auf RC-Servos zugeschnitten ist. Für Anwendungen, bei denen der Duty Cycle wichtiger ist als die Frequenz (z. B. analoge Leistungssteuerung), ist eine andere PWM-Strategie vorzuziehen.

Mehr über analoge Leistungssteuerung mit PWM hier:
PWM-Controller in VHDL erstellen

Bereit, es selbst auszuprobieren? Laden Sie das ZIP-Archiv mit dem vollständigen VHDL-Code, ModelSim-Testbench, iCEcube2-Projekt und Programmierkonfiguration herunter, indem Sie Ihre E-Mail-Adresse unten eingeben.

Lassen Sie mich Ihre Gedanken in den Kommentaren wissen!

VHDL

  1. PWM-Leistungssteller: Pulsweitenmodulierter Lampentreiber bauen
  2. PWM-Controller in VHDL implementieren: Design, Simulation & FPGA-Demo
  3. FPGA Block RAM aus Textdateien mit VHDLs TEXTIO-Bibliothek initialisieren
  4. Echtzeit-Temperaturdaten vom ppDAQC Pi‑Plate zu InitialState streamen – Headless Raspberry Pi
  5. Heimtemperatur mit Raspberry Pi Dashboard überwachen
  6. Schritt-für-Schritt-Anleitung: PLC-Daten mit IIoT extrahieren für Echtzeit-Einblicke
  7. In-Cabin-KI mit Trusted Execution Environment auf sicherem FPGA SoC absichern
  8. Können Ihre Servo-Controller repariert werden? Experten-Einblicke & kosteneffektive Lösungen
  9. Osai-Prima Electro Servo Drive 97942050N: „C Axis drive not ok“ Fehler behoben
  10. 25-kHz-4-Pin-PWM-Lüftersteuerung mit Arduino Uno – Bau, Code & Test