Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial programming >> VHDL

Een 'breathing LED' effect maken met een sinusgolf uit Block RAM

Ik heb opgemerkt dat veel gadgets die ik de afgelopen paar jaar heb gekocht, zijn verschoven van knipperende LED's naar 'breathing LEDs'. Meeste elektronische gadgets bevatten een status-LED wiens gedrag aanwijzingen geeft over wat er binnenin het apparaat gebeurt.

Mijn elektrische tandborstel knipt een LED wanneer het tijd is om op te laden, en mijn mobiele telefoon gebruikt de LED om mijn aandacht te trekken voor een grote verscheidenheid aan redenen. Maar de LED's knipperen niet meer zoals in de oude dagen. Het is meer een analog pulserend effect met continu variërende intensiteit.

De GIF-animatie hieronder toont mijn Logitech-muis die dit effect gebruikt om aan te geven dat hij de batterij oplaadt. Ik noem dit effect breathing LED omdat het lichtintensiteitspatroon vergelijkbaar is in snelheid en versnelling met de menselijke ademcyclus. Het ziet er zo natuurlijk uit omdat de verlichtingscyclus een sinusgolf-patroon volgt.

Een  breathing LED  effect maken met een sinusgolf uit Block RAM

Dit artikel is een vervolg op vorige week's blogpost over pulsbreedtemodulatie (PWM). Vandaag gaan we de PWM-module, de zaagtandteller en de reset-module gebruiken die we in de vorige tutorial creëerden. Klik op de link hieronder om het artikel over PWM te lezen!

Hoe een PWM-controller in VHDL te maken

Sinusgolf-waarden opslaan in Block RAM

Hoewel het mogelijk is om een sinusgolf te genereren met digitale signaalverwerking (DSP) primitieven in de FPGA, is een eenvoudigere manier het opslaan van de samplepunten in block RAM. In plaats van de waarden tijdens runtime te berekenen, berekenen we een reeks sinuswaarden tijdens de synthese en creëren we een read-only memory (ROM) om ze in op te slaan.

Overweeg het minimale voorbeeld hieronder, dat laat zien hoe een volledige sinusgolf opgeslagen wordt in een 3×3 bits ROM. Zowel de adres-invoer als de data-uitgang zijn drie bits breed, wat betekent dat ze een integer-waarde op het bereik 0 tot 7 kunnen voorstellen. We kunnen acht sinuswaarden in de ROM opslaan, en de resolutie van de data is ook 0 tot 7.

Een  breathing LED  effect maken met een sinusgolf uit Block RAM

De trigonometrische sinusfunctie produceert een getal op het bereik [-1, 1] voor elke hoekinvoer, x. Ook herhaalt de cyclus zich wanneer x ≥ 2π. Daarom is het voldoende om alleen de sinuswaarden op te slaan van nul tot en met, maar niet inclusief 2π. De sinuswaarde voor 2π is dezelfde als de sinus voor nul. De illustratie hierboven toont dit concept. We slaan sinuswaarden op van nul tot \frac{7\pi}{4}, wat de laatste gelijkmatig verdeelde stap is voor de volledige 2π-cirkel.

Digitale logica kan geen reële waarden, zoals de hoek of sinuswaarden, met oneindige precisie voorstellen. Dat is het geval in elk computersysteem. Zelfs bij gebruik van dubbele-precisie drijvende-komma getallen, is het slechts een benadering. Dat is hoe binaire getallen werken, en onze sinus-ROM is niet anders.

Om het meest uit de beschikbare databits te halen, voegen we een offset en een schaal toe aan de sinuswaarden wanneer we de ROM-inhoud berekenen. De laagst mogelijke sinuswaarde van -1 map naar de datawaarde 0, terwijl de hoogst mogelijke sinuswaarde van 1 vertaalt naar 2^{\mathit{data\_bits}-1}, zoals getoond door de formule hieronder.

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

Om een ROM-adres om te zetten in een hoek, x, kunnen we de volgende formule gebruiken:

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

Natuurlijk geeft deze methode je geen universele hoek-naar-sinuswaarde-converter. Als dat wat je wilt, zul je extra logica of DSP-primitieven moeten gebruiken om de adres-invoer en de data-uitgang te schalen. Maar voor veel toepassingen is een sinusgolf voorgesteld als een unsigned integer goed genoeg. En zoals we in de volgende sectie zullen zien, is het precies wat we nodig hebben voor ons voorbeeld LED-pulsproject.

Sine ROM Module

De sine ROM-module die in dit artikel gepresenteerd wordt, zal block RAM infereren op de meeste FPGA-architecturen. Overweeg de breedte- en diepte-generics af te stemmen op de geheugenprimitieven van je doel-FPGA. Dat geeft je de beste resource-utilisatie. Je kunt altijd naar het voorbeeld Lattice-project verwijzen als je onzeker bent over hoe de sine ROM te gebruiken voor een echt FPGA-project.

Laat je e-mail in het formulier hieronder om de VHDL-bestanden en ModelSim / iCEcube2-projecten te downloaden!

De Entiteit

De onderstaande code toont de entiteit van de sine ROM-module. Het bevat twee generieke invoeren waarmee je de breedte en diepte van de geïnfererde block RAM kunt specificeren. Ik gebruik range-specificaties op de constanten om ongewilde integer-overflow te voorkomen. Meer daarover later in dit 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; 

De poortdeclaratie heeft een klokinvoer, maar geen reset omdat RAM-primitieven geen reset kunnen hebben. De addr invoer is waar we de geschaalde hoek (x) waarde toewijzen, en de data uitgang is waar de geschaalde sinuswaarde zal verschijnen.

Type Declaraties

Bovenaan het declaratieve gebied van het VHDL-bestand declareren we een type en een subtype voor ons ROM-opslagobject. Het addr_range subtype is een integer-bereik gelijk aan het aantal slots in onze RAM, en het rom_type beschrijft een 2D-array die alle sinuswaarden zal opslaan.

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

We gaan de opslagsignaal nog niet declareren. Eerst moeten we de functie definiëren die de sinuswaarden produceert, die we kunnen gebruiken om de RAM in een ROM te veranderen. We moeten het bovenaan de signaaldeclaratie declareren zodat we de functie kunnen gebruiken om een initiële waarde toe te kennen aan het ROM-opslagsignaal.

Let op dat we de addr_bits generiek gebruiken als basis voor het definiëren van addr_range. Dat is de reden waarom ik een max-waarde van 30 voor addr_bits moest specificeren. Want voor grotere waarden zal de 2**addr_bits - 1 berekening overflowen. VHDL-integers zijn 32-bits lang, hoewel dat gaat veranderen met VHDL-2019, wat 64-bit integers gebruikt. Maar voor nu moeten we met deze beperking leven wanneer we integers in VHDL gebruiken tot de tools VHDL-2019 gaan ondersteunen.

Functie voor het Genereren van Sinuswaarden

De onderstaande code toont de init_rom functie die de sinuswaarden genereert. Het retourneert een rom_type object, wat de reden is waarom we het type eerst moeten declareren, dan de functie, en tenslotte de ROM-constante. Ze zijn afhankelijk van elkaar in die exacte volgorde.

 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; 

De functie gebruikt een paar convenience-variabelen, waaronder rom_v, een lokale kopie van de array die we vullen met sinuswaarden. Binnen het subprogramma gebruiken we een for-lus om over het adresbereik te itereren, en voor elke ROM-slot berekenen we de sinuswaarde met de formules die ik eerder beschreef. En aan het einde retourneren we de rom_v variabele die nu alle sinus-samples bevat.

data_bits generiek moest beperken tot 31 bits. Het zal overflowen voor elke grotere bitlengte.

 constant rom : rom_type := init_rom; 

Onder de init_rom functiedefinitie gaan we verder met het declareren van het rom object als een constante. Een ROM is simpelweg een RAM waar je nooit naar schrijft, dus dat is prima. En nu is het tijd om onze functie te gebruiken. We roepen init_rom aan om de initiële waarden te genereren, zoals getoond in de code hierboven.

Het ROM-proces

De enige logica in het sine ROM-bestand is het rather simpele proces dat hieronder staat. Het beschrijft een block RAM met een enkele read-poort.

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

Top Module

Dit ontwerp is een vervolg van het PWM-project dat ik in mijn vorige blogpost presenteerde. Het heeft een reset-module, een PWM-generator-module en een vrijlopende klokcyclus-teller (zaagtandteller) module. Verwijs naar vorige week's artikel om te zien hoe deze modules werken.

Het diagram hieronder toont de verbindingen tussen de submodules in de top-module.

Een  breathing LED  effect maken met een sinusgolf uit Block RAM

De onderstaande code toont het declaratieve gebied van het top VHDL-bestand. In vorige week's PWM-ontwerp was het duty_cycle object een alias voor de MSB's van de cnt teller. Maar dat zou nu niet werken omdat de uitgang van de sine ROM de duty cycle zal sturen, dus ik heb het vervangen door een echt signaal. Bovendien heb ik een nieuwe alias aangemaakt met de naam addr die de MSB's van de teller is. We zullen het gebruiken als adresinvoer voor de ROM.

 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); -- Gebruik MSB's van teller voor sine ROM adresinvoer alias addr is cnt(cnt'high downto cnt'length - pwm_bits); 

Je kunt zien hoe je onze nieuwe sine ROM instantieert in de top-module in de onderstaande listing. We stellen de breedte en diepte van de RAM in om de lengte van de PWM-module's interne teller te volgen. De data uitgang van de ROM stuurt de duty_cycle invoer van de PWM-module. De waarde op het duty_cycle signaal zal een sinusgolf-patroon vertonen wanneer we de RAM-slots één na de ander uitlezen.

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

De Sinusgolf ROM Simuleren

De onderstaande afbeelding toont de waveform van de top-module simulatie in ModelSim. Ik heb de presentatie van het unsigned duty_cycle signaal veranderd naar een analoog formaat zodat we de sinusgolf kunnen observeren.

Een  breathing LED  effect maken met een sinusgolf uit Block RAM

Het is de led_5 top-level uitgang die het PWM-signaal draagt, wat de externe LED stuurt. We kunnen zien dat de uitgang snel verandert wanneer de duty cycle stijgt of daalt. Maar wanneer de duty cycle aan de top van de sinusgolf is, is led_5 een constante '1'. Wanneer de golf aan de bodem van de helling is, blijft de uitgang kort op '0'.

Wil je het op je thuiscomputer uitproberen? Vul je e-mailadres in het formulier hieronder in om de VHDL-bestanden en de ModelSim en iCEcube2-projecten te ontvangen!

LED Breathing Implementeren op de FPGA

Ik gebruikte de Lattice iCEcube2 software om het ontwerp op het iCEstick FPGA-board te implementeren. Gebruik het formulier hierboven om het project te downloaden en het op je board uit te proberen als je een iCEstick bezit!

De onderstaande listing toont de resource-gebruik, zoals gerapporteerd door de Synplify Pro software die bij iCEcube2 hoort. Het toont dat het ontwerp één block RAM-primitief gebruikt. Dat is onze sine 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%) 

Na het routeren van het ontwerp in iCEcube2, vind je het .bin bestand in de led_breathing_Implmnt\sbt\outputs\bitmap map binnen de Lattice_iCEcube2_proj project-directory.

Je kunt de Lattice Diamond Programmer Standalone software gebruiken om de FPGA te programmeren, zoals getoond in de iCEstick-gebruikershandleiding. Dat heb ik gedaan, en de GIF-animatie hieronder toont het resultaat. De lichtintensiteit van de LED oscilleert met een sinusgolf-patroon. Het ziet er heel natuurlijk uit, en de LED lijkt te "ademen" als je wat verbeelding inzet.

Een  breathing LED  effect maken met een sinusgolf uit Block RAM

Eindgedachten

Block RAM gebruiken voor het opslaan van vooraf berekende sinuswaarden is redelijk rechttoe rechtaan. Maar er zijn een paar beperkingen die deze methode alleen geschikt maken voor sinusgolven met beperkte X- en Y-resoluties.

De eerste reden die me in gedachten komt is de 32-bit limiet voor integer-waarden die ik eerder besprak. Ik ben er zeker van dat je dit probleem kunt oplossen door de sinuswaarde slimmer te berekenen, maar dat is niet alles.

Voor elke bit die je het ROM-adres uitbreidt, verdubbel je het RAM-gebruik. Als je hoge precisie op de X-as nodig hebt, is er misschien niet genoeg RAM, zelfs op een grotere FPGA.

Als het aantal bits dat gebruikt wordt voor de Y-as sinuswaarden de native RAM-diepte overschrijdt, zal de synthese-tool extra RAMs of LUTs gebruiken om de ROM te implementeren. Het zal meer van je resource-budget opeten naarmate je de Y-precisie verhoogt.

In theorie hoeven we alleen één kwadrant van de sinusgolf op te slaan. Daarom kun je met een kwart van het RAM-gebruik uitkomen als je een finite-state machine (FSM) gebruikt om de ROM-uitlezing te sturen. Het zou de sinus-kwadrant moeten inverteren voor alle vier permutaties van de X- en Y-assen. Dan kun je een volledige sinusgolf bouwen vanuit de enkele kwadrant die in block RAM opgeslagen is.

Helaas is het moeilijk om alle vier segmenten soepel te laten aansluiten. Twee gelijke samples in de knopen aan de top en bodem van de sinusgolf vervormt de data door platte plekken op de sinusgolf te creëren. Ruisonderdrukken te introduceren verslaat het doel van alleen het kwadrant op te slaan om de precisie van de sinusgolf te verbeteren.

VHDL

  1. Frictieloze gebruikerservaringen creëren: Een UX-design blauwdruk
  2. PLC-functieblokken Aanroepen via OPC UA-clients: Een Praktische Gids
  3. Digitale productie revolutieert anatomische modellering in moderne geneeskunde
  4. Bouw Uw Eerste AWS CloudFormation Template – Een Praktische Gids
  5. Dynamische string-lijsten in VHDL: Best Practices & Voorbeeld
  6. Een Tcl-gedreven testbench maken voor een VHDL code-lock module
  7. Een PWM-controller implementeren in VHDL: Ontwerp, Simulatie en FPGA-demo
  8. FPGA Block RAM initialiseren vanuit tekstbestanden met VHDL's TEXTIO-bibliotheek
  9. Een self-checking testbench in VHDL bouwen – Een praktische gids
  10. Een dynamische gelinkte lijst in VHDL implementeren met protected types en access pointers
  11. Een Array van Objecten in Java aanmaken: Stap-voor-stap gids