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

So erzeugen Sie einen atmenden LED-Effekt mit einer im Block-RAM gespeicherten Sinuswelle

Mir ist aufgefallen, dass viele der Gadgets, die ich in den letzten Jahren gekauft habe, sich von LED-Blinken zu LED-Atmung verlagert haben. Die meisten elektronischen Spielereien enthalten eine Status-LED, deren Verhalten Aufschluss darüber gibt, was im Inneren des Geräts vor sich geht.

Meine elektrische Zahnbürste lässt eine LED aufleuchten, wenn es an der Zeit ist, sie aufzuladen, und mein Mobiltelefon verwendet die LED, um meine Aufmerksamkeit aus einer Vielzahl von Gründen zu erregen. Aber die LEDs blinken nicht mehr wie früher. Es ist eher wie ein analoger pulsierender Effekt mit kontinuierlich variierender Intensität.

Die GIF-Animation unten zeigt meine Logitech-Maus, die diesen Effekt verwendet, um anzuzeigen, dass sie den Akku auflädt. Ich nenne diesen Effekt atmende LED weil das Lichtintensitätsmuster in Geschwindigkeit und Beschleunigung dem menschlichen Atemzyklus ähnelt. Es sieht so natürlich aus, weil der Beleuchtungszyklus einem Sinuswellenmuster folgt.

Dieser Artikel ist eine Fortsetzung des Blogbeitrags der letzten Woche über Pulsweitenmodulation (PWM). Heute werden wir das PWM-Modul, den Sägezahnzähler und das Reset-Modul verwenden, die wir im vorherigen Tutorial erstellt haben. Klicken Sie auf den Link unten, um den Artikel über PWM zu lesen!

So erstellen Sie einen PWM-Controller in VHDL

Sinuswellenwerte im Block-RAM speichern

Während es möglich ist, eine Sinuswelle mit Hilfe von Grundelementen der digitalen Signalverarbeitung (DSP) im FPGA zu erzeugen, ist es einfacher, die Abtastpunkte im Block-RAM zu speichern. Anstatt die Werte während der Laufzeit zu berechnen, berechnen wir während der Synthese eine Reihe von Sinuswerten und erstellen einen Nur-Lese-Speicher (ROM), um sie darin zu speichern.

Betrachten Sie das folgende Minimalbeispiel, das zeigt, wie eine vollständige Sinuswelle in einem 3 × 3-Bit-ROM gespeichert wird. Sowohl der Adresseingang als auch der Datenausgang sind drei Bit breit, was bedeutet, dass sie einen ganzzahligen Wert im Bereich von 0 bis 7 darstellen können. Wir können acht Sinuswerte im ROM speichern, und die Auflösung der Daten ist ebenfalls 0 bis 7 .

Die trigonometrische Sinusfunktion erzeugt eine Zahl im Bereich [-1, 1] für jede Winkeleingabe, x . Außerdem wiederholt sich der Zyklus, wenn x ≥ 2π. Daher reicht es aus, nur die Sinuswerte von Null bis aufwärts, aber ohne 2π zu speichern. Der Sinuswert für 2π ist der gleiche wie der Sinus für Null. Die obige Abbildung zeigt dieses Konzept. Wir speichern Sinuswerte von null bis \frac{7\pi}{4}, was der letzte gleichmäßige Abstandsschritt vor dem vollständigen 2π-Kreis ist.

Digitale Logik kann reale Werte wie Winkel- oder Sinuswerte nicht unendlich genau darstellen. Das ist bei jedem Computersystem der Fall. Selbst bei Verwendung von Gleitkommazahlen mit doppelter Genauigkeit ist dies nur eine Annäherung. So funktionieren Binärzahlen und unser Sinus-ROM ist nicht anders.

Um das Beste aus den verfügbaren Datenbits herauszuholen, fügen wir bei der Berechnung des ROM-Inhalts einen Offset und eine Skalierung zu den Sinuswerten hinzu. Der niedrigstmögliche Sinuswert von -1 wird dem Datenwert 0 zugeordnet, während der höchstmögliche Sinuswert von 1 in 2^{\mathit{data\_bits}-1} übersetzt wird, wie die folgende Formel zeigt.

\mathit{data} =\mathit{Round}\left(\frac{(1 + \sin \mathit{addr}) * (2^\mathit{data\_bits} - 1)}{2}\right)

Um eine ROM-Adresse in einen Winkel x zu übersetzen, können wir die folgende Formel verwenden:

x =\frac{\mathit{addr} * 2\pi}{2^\mathit{addr\_bits}}

Natürlich gibt Ihnen diese Methode keinen universellen Winkel-zu-Sinus-Wert-Konverter. Wenn Sie das möchten, müssen Sie zusätzliche Logik oder DSP-Primitive verwenden, um den Adresseingang und den Datenausgang zu skalieren. Aber für viele Anwendungen ist eine als Ganzzahl ohne Vorzeichen dargestellte Sinuswelle gut genug. Und wie wir im nächsten Abschnitt sehen werden, ist es genau das, was wir für unser beispielhaftes LED-Pulsing-Projekt benötigen.

Sinus-ROM-Modul

Das in diesem Artikel vorgestellte Sinus-ROM-Modul wird Block-RAM auf den meisten FPGA-Architekturen ableiten. Erwägen Sie, die Generics für Breite und Tiefe an die Speicherprimitive Ihres Ziel-FPGA anzupassen. Dadurch erhalten Sie die beste Ressourcennutzung. Sie können sich immer auf das Lattice-Beispielprojekt beziehen, wenn Sie sich nicht sicher sind, wie Sie das Sinus-ROM für ein echtes FPGA-Projekt verwenden sollen.

Hinterlassen Sie Ihre E-Mail-Adresse im untenstehenden Formular, um die VHDL-Dateien und ModelSim / iCEcube2-Projekte herunterzuladen!

Die Entität

Der folgende Code zeigt die Entität des Sinus-ROM-Moduls. Es enthält zwei generische Eingaben, mit denen Sie die Breite und Tiefe des abgeleiteten Block-RAM angeben können. Ich verwende Bereichsbezeichner für die Konstanten, um einen unbeabsichtigten ganzzahligen Überlauf zu verhindern. Mehr dazu später in diesem Artikel.

entity sine_rom is
  generic (
    addr_bits : integer range 1 to 30;
    data_bits : integer range 1 to 31
  );
  port (
    clk : in std_logic;
    addr : in unsigned(addr_bits - 1 downto 0);
    data : out unsigned(data_bits - 1 downto 0)
  );
end sine_rom; 

Die Port-Deklaration hat einen Takteingang, aber kein Zurücksetzen, da RAM-Primitive nicht zurückgesetzt werden können. Die Adresse input ist, wo wir den skalierten Winkel zuweisen (x )-Wert und die Daten Ausgabe ist, wo der skalierte Sinuswert erscheint.

Typdeklarationen

Oben im deklarativen Bereich der VHDL-Datei deklarieren wir einen Typ und einen Untertyp für unser ROM-Speicherobjekt. Der adr_range subtype ist ein ganzzahliger Bereich, der der Anzahl der Slots in unserem RAM und dem rom_type entspricht beschreibt ein 2D-Array, das alle Sinuswerte speichert.

subtype addr_range is integer range 0 to 2**addr_bits - 1;
type rom_type is array (addr_range) of unsigned(data_bits - 1 downto 0);

Wir werden das Speichersignal jedoch noch nicht deklarieren. Zuerst müssen wir die Funktion definieren, die die Sinuswerte erzeugt, die wir verwenden können, um das RAM in ein ROM umzuwandeln. Wir müssen es über der Signaldeklaration deklarieren, damit wir die Funktion verwenden können, um dem ROM-Speichersignal einen Anfangswert zuzuweisen.

Beachten Sie, dass wir die addr_bits verwenden generisch als Grundlage für die Definition von addr_range . Aus diesem Grund musste ich für addr_bits einen maximalen Wert von 30 angeben . Denn bei größeren Werten wird der 2**addr_bits - 1 Die Berechnung läuft über. VHDL-Ganzzahlen sind 32 Bit lang, obwohl sich das mit VHDL-2019 ändern wird, das 64-Bit-Ganzzahlen verwendete. Aber vorerst müssen wir mit dieser Einschränkung bei der Verwendung von Ganzzahlen in VHDL leben, bis die Tools beginnen, VHDL-2019 zu unterstützen.

Funktion zur Erzeugung von Sinuswerten

Der folgende Code zeigt das init_rom Funktion, die die Sinuswerte erzeugt. Es gibt einen rom_type zurück Objekt, weshalb wir zuerst den Typ deklarieren müssen, dann die Funktion und schließlich die ROM-Konstante. Sie sind in genau dieser Reihenfolge voneinander abhängig.

function init_rom return rom_type is
  variable rom_v : rom_type;
  variable angle : real;
  variable sin_scaled : real;
begin

  for i in addr_range loop

    angle := real(i) * ((2.0 * MATH_PI) / 2.0**addr_bits);
    sin_scaled := (1.0 + sin(angle)) * (2.0**data_bits - 1.0) / 2.0;
    rom_v(i) := to_unsigned(integer(round(sin_scaled)), data_bits);
    
  end loop;
  
  return rom_v;
end init_rom;

Die Funktion verwendet einige praktische Variablen, einschließlich rom_v , eine lokale Kopie des Arrays, die wir mit Sinuswerten füllen. Innerhalb des Unterprogramms verwenden wir eine for-Schleife, um über den Adressbereich zu iterieren, und für jeden ROM-Steckplatz berechnen wir den Sinuswert mithilfe der zuvor beschriebenen Formeln. Und am Ende geben wir das rom_v zurück Variable, die mittlerweile alle Sinus-Samples enthält.

Die Integer-Konvertierung in der letzten Zeile der for-Schleife ist der Grund, warum ich die data_bits einschränken musste generisch auf 31 Bit. Bei größeren Bitlängen wird es überlaufen.

constant rom : rom_type := init_rom;

Unter dem init_rom Funktionsdefinition deklarieren wir als nächstes das rom Objekt als Konstante. Ein ROM ist einfach ein RAM, in das Sie nie schreiben, also ist das vollkommen in Ordnung. Und jetzt ist es an der Zeit, unsere Funktion zu nutzen. Wir nennen init_rom um die Anfangswerte zu generieren, wie im obigen Code gezeigt.

Der ROM-Prozess

Die einzige Logik in der Sinus-ROM-Datei ist der ziemlich einfache Prozess, der unten aufgeführt ist. Es beschreibt ein Block-RAM mit einem einzigen Leseport.

ROM_PROC : process(clk)
begin
  if rising_edge(clk) then
    data <= rom(to_integer(addr));
  end if;
end process;

Oberes Modul

Dieses Design ist eine Fortsetzung des PWM-Projekts, das ich in meinem vorherigen Blogbeitrag vorgestellt habe. Es verfügt über ein Reset-Modul, ein PWM-Generatormodul und ein freilaufendes Taktzykluszählermodul (Sägezahnzähler). Lesen Sie den Artikel der letzten Woche, um zu sehen, wie diese Module funktionieren.

Das folgende Diagramm zeigt die Verbindungen zwischen den Submodulen im oberen Modul.

Der folgende Code zeigt den deklarativen Bereich der obersten VHDL-Datei. Im PWM-Design der letzten Woche ist der duty_cycle Objekt war ein Alias ​​für die MSBs des cnt Zähler. Aber das würde jetzt nicht funktionieren, weil der Ausgang des Sinus-ROM den Arbeitszyklus steuert, also habe ich ihn durch ein tatsächliches Signal ersetzt. Außerdem habe ich einen neuen Alias ​​mit dem Namen addr erstellt das sind die MSBs des Zählers. Wir werden es als Adresseingabe für das ROM verwenden.

signal rst : std_logic;
signal cnt : unsigned(cnt_bits - 1 downto 0);
signal pwm_out : std_logic;
signal duty_cycle : unsigned(pwm_bits - 1 downto 0);

-- Use MSBs of counter for sine ROM address input
alias addr is cnt(cnt'high downto cnt'length - pwm_bits);

In der folgenden Auflistung können Sie sehen, wie Sie unser neues Sinus-ROM im obersten Modul instanziieren. Wir stellen die Breite und Tiefe des RAM so ein, dass sie der Länge des internen Zählers des PWM-Moduls folgen. Die Daten Ausgabe vom ROM steuert den duty_cycle Eingang zum PWM-Modul. Der Wert für duty_cycle Signal wird ein Sinuswellenmuster darstellen, wenn wir die RAM-Slots nacheinander auslesen.

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

Simulation des Sinuswellen-ROM

Das Bild unten zeigt die Wellenform der Top-Modul-Simulation in ModelSim. Ich habe die Darstellung des unsignierten duty_cycle geändert Signal in ein analoges Format, damit wir die Sinuswelle beobachten können.

Es ist die led_5 Top-Level-Ausgang, der das PWM-Signal trägt, das die externe LED steuert. Wir können sehen, dass sich die Ausgabe schnell ändert, wenn das Tastverhältnis steigt oder fällt. Aber wenn das Tastverhältnis am oberen Ende der Sinuswelle liegt, led_5 ist eine konstante „1“. Wenn sich die Welle am unteren Ende der Flanke befindet, bleibt der Ausgang kurz auf „0“.

Möchten Sie es auf Ihrem Heimcomputer ausprobieren? Geben Sie Ihre E-Mail-Adresse in das untenstehende Formular ein, um die VHDL-Dateien und die ModelSim- und iCEcube2-Projekte zu erhalten!

Implementierung von LED-Atmung auf dem FPGA

Ich habe die Lattice iCEcube2-Software verwendet, um das Design auf dem iCEstick-FPGA-Board zu implementieren. Verwenden Sie das obige Formular, um das Projekt herunterzuladen und auf Ihrem Board auszuprobieren, wenn Sie einen iCEstick besitzen!

Die folgende Auflistung zeigt die Ressourcennutzung, wie sie von der Synplify Pro-Software gemeldet wird, die mit iCEcube2 geliefert wird. Es zeigt, dass das Design ein Block-RAM-Grundelement verwendet. Das ist unser Sinus-ROM.

Resource Usage Report for led_breathing 

Mapping to part: ice40hx1ktq144
Cell usage:
GND             4 uses
SB_CARRY        31 uses
SB_DFF          5 uses
SB_DFFSR        39 uses
SB_GB           1 use
SB_RAM256x16    1 use
VCC             4 uses
SB_LUT4         65 uses

I/O ports: 7
I/O primitives: 7
SB_GB_IO       1 use
SB_IO          6 uses

I/O Register bits:                  0
Register bits not including I/Os:   44 (3%)

RAM/ROM usage summary
Block Rams : 1 of 16 (6%)

Total load per clock:
   led_breathing|clk: 1

@S |Mapping Summary:
Total  LUTs: 65 (5%)

Nachdem Sie das Design in iCEcube2 geroutet haben, finden Sie die .bin Datei in led_breathing_Implmnt\sbt\outputs\bitmap Ordner innerhalb des Lattice_iCEcube2_proj Projektverzeichnis.

Sie können die Lattice Diamond Programmer Standalone-Software verwenden, um das FPGA zu programmieren, wie im iCEstick-Benutzerhandbuch gezeigt. Das habe ich getan, und die GIF-Animation unten zeigt das Ergebnis. Die Lichtintensität der LED oszilliert mit einem Sinuswellenmuster. Es sieht sehr natürlich aus und die LED scheint zu "atmen", wenn Sie etwas Fantasie hineinstecken.

Abschließende Gedanken

Die Verwendung von Block-RAM zum Speichern vorberechneter Sinuswerte ist ziemlich einfach. Aber es gibt ein paar Einschränkungen, die diese Methode nur für Sinuswellen mit begrenzter X- und Y-Auflösung geeignet machen.

Der erste Grund, der mir in den Sinn kommt, ist die 32-Bit-Grenze für ganzzahlige Werte, die ich zuvor besprochen habe. Ich bin sicher, Sie können dieses Problem lösen, indem Sie den Sinuswert geschickter berechnen, aber das ist noch nicht alles.

Für jedes Bit, um das Sie die ROM-Adresse erweitern, verdoppeln Sie die RAM-Nutzung. Wenn Sie eine hohe Präzision auf der X-Achse benötigen, ist selbst auf einem größeren FPGA möglicherweise nicht genügend RAM vorhanden.

Wenn die Anzahl der für die Sinuswerte der Y-Achse verwendeten Bits die native RAM-Tiefe überschreitet, verwendet das Synthesetool zusätzliche RAMs oder LUTs, um das ROM zu implementieren. Es wird mehr von Ihrem Ressourcenbudget verschlingen, wenn Sie die Y-Präzision erhöhen.

Theoretisch brauchen wir nur einen Quadranten der Sinuswelle zu speichern. Daher könnten Sie mit einem Viertel der RAM-Nutzung davonkommen, wenn Sie eine endliche Zustandsmaschine (FSM) verwenden, um das ROM-Auslesen zu steuern. Es müsste den Sinusquadranten für alle vier Permutationen der X- und Y-Achse invertieren. Dann könnten Sie aus dem einzelnen Quadranten, der im Block-RAM gespeichert ist, eine vollständige Sinuswelle erstellen.

Leider ist es schwierig, alle vier Segmente reibungslos zu verbinden. Zwei gleiche Samples in den Gelenken am oberen und unteren Rand der Sinuswelle verzerren die Daten, indem sie flache Punkte auf der Sinuswelle erzeugen. Das Einführen von Rauschen vereitelt den Zweck, nur den Quadranten zu speichern, um die Genauigkeit der Sinuswelle zu verbessern.


VHDL

  1. So erstellen Sie eine CloudFormation-Vorlage mit AWS
  2. Wie man reibungslose UX erstellt
  3. So erstellen Sie eine Liste von Zeichenfolgen in VHDL
  4. So erstellen Sie eine Tcl-gesteuerte Testbench für ein VHDL-Code-Sperrmodul
  5. So initialisieren Sie RAM aus einer Datei mit TEXTIO
  6. So erstellen Sie eine selbstüberprüfende Testbench
  7. So erstellen Sie eine verknüpfte Liste in VHDL
  8. So erstellen Sie ein Array von Objekten in Java
  9. Wie Mediziner die digitale Fertigung nutzen, um anatomische Modelle der nächsten Generation zu erstellen
  10. So rufen Sie einen Funktionsblock von einem OPC UA-Client mithilfe eines Informationsmodells auf