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

Hoe een ademend LED-effect te creëren met behulp van een sinusgolf die is opgeslagen in blok RAM

Ik heb gemerkt dat veel van de gadgets die ik de afgelopen jaren heb gekocht, zijn verschoven van LED-knippering naar led-ademhaling. De meeste elektronische gadgets bevatten een status-LED waarvan het gedrag indicaties geeft van wat er in het apparaat gebeurt.

Op mijn elektrische tandenborstel knippert een led wanneer het tijd is om hem op te laden, en mijn mobiele telefoon gebruikt de led om verschillende redenen om mijn aandacht te trekken. Maar de LED's knipperen niet zoals vroeger. Het is meer een analoog pulserend effect met een continu variërende intensiteit.

De onderstaande GIF-animatie laat zien dat mijn Logitech-muis dit effect gebruikt om aan te geven dat de batterij wordt opgeladen. Ik noem dit effect ademhalings-LED omdat het lichtintensiteitspatroon qua snelheid en versnelling vergelijkbaar is met de menselijke ademhalingscyclus. Het ziet er zo natuurlijk uit omdat de verlichtingscyclus een sinusgolfpatroon volgt.

Dit artikel is een vervolg op de blogpost van vorige week over pulsbreedtemodulatie (PWM). Vandaag gaan we de PWM-module, de zaagtandteller en de resetmodule gebruiken die we in de vorige tutorial hebben gemaakt. Klik op de onderstaande link om het artikel over PWM te lezen!

Een PWM-controller maken in VHDL

Sinusgolfwaarden opslaan in blok-RAM

Hoewel het mogelijk is om een ​​sinusgolf te genereren met behulp van primitieven voor digitale signaalverwerking (DSP) in de FPGA, is een meer rechttoe rechtaan manier om de monsterpunten op te slaan in blok-RAM. In plaats van de waarden tijdens runtime te berekenen, berekenen we een reeks sinuswaarden tijdens de synthese en creëren we een alleen-lezen geheugen (ROM) om ze in op te slaan.

Overweeg het onderstaande minimale voorbeeld, dat laat zien hoe u een volledige sinusgolf opslaat in een ROM van 3 × 3 bits. Zowel de adresinvoer als de gegevensuitvoer zijn drie bits breed, wat betekent dat ze een geheel getal kunnen vertegenwoordigen in het bereik van 0 tot 7. We kunnen acht sinuswaarden opslaan in de ROM en de resolutie van de gegevens is ook 0 tot 7 .

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

Digitale logica kan echte waarden, zoals de hoek- of sinuswaarden, niet met oneindige precisie weergeven. Dat is het geval in elk computersysteem. Zelfs bij het gebruik van drijvende-kommagetallen met dubbele precisie is dit slechts een benadering. Zo werken binaire getallen, en onze sinus-ROM is niet anders.

Om het maximale 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 komt overeen met de gegevenswaarde 0, terwijl de hoogst mogelijke sinuswaarde van 1 zich vertaalt in 2^{\mathit{data\_bits}-1}, zoals blijkt uit de onderstaande formule.

\mathit{data} =\mathit{Ronde}\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 u geen universele hoek-naar-sinuswaarde-omzetter. Als dat is wat u wilt, moet u extra logica of DSP-primitieven gebruiken om de adresinvoer en de gegevensuitvoer te schalen. Maar voor veel toepassingen is een sinusgolf weergegeven als een geheel getal zonder teken goed genoeg. En zoals we in het volgende gedeelte zullen zien, is dit precies wat we nodig hebben voor ons voorbeeldproject met pulsen van LED's.

Sinus-ROM-module

De sinus-ROM-module die in dit artikel wordt gepresenteerd, zal op de meeste FPGA-architecturen blok-RAM afleiden. Overweeg om de generieke breedte en diepte af te stemmen op de geheugenprimitieven van uw doel-FPGA. Dat geeft u het beste gebruik van hulpbronnen. U kunt altijd het voorbeeld Lattice-project raadplegen als u niet zeker weet hoe u de sinus-ROM voor een echt FPGA-project moet gebruiken.

Laat je e-mailadres achter in het onderstaande formulier om de VHDL-bestanden en ModelSim / iCEcube2-projecten te downloaden!

De entiteit

De onderstaande code toont de entiteit van de sinus-ROM-module. Het bevat twee generieke ingangen waarmee u de breedte en diepte van het afgeleide blok-RAM kunt specificeren. Ik gebruik bereikspecificaties op de constanten om onbedoelde overloop van gehele getallen te voorkomen. Meer daarover verderop 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 toewijzen (x ) waarde, en de gegevens output is waar de geschaalde sinuswaarde zal verschijnen.

Type aangiften

Bovenaan de declaratieve regio van het VHDL-bestand declareren we een type en een subtype voor ons ROM-opslagobject. De addr_range subtype is een geheel getal dat gelijk is aan het aantal slots in ons RAM-geheugen, en het rom_type beschrijft een 2D-array waarin alle sinuswaarden worden opgeslagen.

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 het opslagsignaal echter nog niet declareren. Eerst moeten we de functie definiëren die de sinuswaarden zal produceren, die we kunnen gebruiken om de RAM in een ROM te veranderen. We moeten het boven de signaaldeclaratie declareren, zodat we de functie kunnen gebruiken om een ​​beginwaarde toe te kennen aan het ROM-opslagsignaal.

Merk op dat we de addr_bits . gebruiken generiek als basis voor het definiëren van addr_range . Dat is de reden waarom ik een maximale waarde van 30 moest specificeren voor addr_bits . Omdat voor grotere waarden de 2**addr_bits - 1 berekening zal overlopen. VHDL-getallen zijn 32-bits lang, hoewel dat op het punt staat te veranderen met VHDL-2019, dat 64-bits gehele getallen gebruikte. Maar voor nu moeten we met deze beperking leven bij het gebruik van gehele getallen in VHDL totdat 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, daarom moeten we eerst het type declareren, dan de functie en tenslotte de ROM-constante. Ze zijn in die exacte volgorde van elkaar afhankelijk.

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 gemaksvariabelen, waaronder rom_v , een lokale kopie van de array die we vullen met sinuswaarden. Binnen het subprogramma gebruiken we een for-lus om het adresbereik te herhalen, en voor elk ROM-slot berekenen we de sinuswaarde met behulp van de formules die ik eerder heb beschreven. En uiteindelijk geven we de rom_v . terug variabele die nu alle sinussen bevat.

De integer-conversie op de laatste regel in de for-loop is de reden waarom ik de data_bits moest beperken generiek tot 31 bits. Het zal overlopen voor grotere bitlengtes.

constant rom : rom_type := init_rom;

Onder de init_rom functiedefinitie, gaan we verder met het declareren van de rom object als constante. Een ROM is gewoon een RAM waarnaar u nooit schrijft, dus dat is prima. En nu is het tijd om onze functie te gebruiken. We noemen init_rom om de initiële waarden te genereren, zoals weergegeven in de bovenstaande code.

Het ROM-proces

De enige logica in het sinus-ROM-bestand is het vrij eenvoudige proces dat hieronder wordt vermeld. Het beschrijft een blok-RAM met een enkele leespoort.

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

Bovenste module

Dit ontwerp is een voortzetting van het PWM-project dat ik in mijn vorige blogpost presenteerde. Het heeft een resetmodule, een PWM-generatormodule en een vrijlopende klokcyclusteller (zaagtandteller). Raadpleeg het artikel van vorige week om te zien hoe deze modules werken.

Het onderstaande schema toont de verbindingen tussen de submodules in de bovenste module.

De onderstaande code toont het declaratieve gebied van het bovenste VHDL-bestand. In het PWM-ontwerp van vorige week, de duty_cycle object was een alias voor de MSB's van de cnt balie. Maar dat zou nu niet werken omdat de uitvoer van de sinus-ROM de duty-cycle zal regelen, dus ik heb het vervangen door een echt signaal. Bovendien heb ik een nieuwe alias gemaakt met de naam addr dat zijn de MSB's van de teller. 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);

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

U kunt zien hoe u onze nieuwe sinus-ROM kunt instantiëren in de bovenste module in de onderstaande lijst. We hebben de breedte en diepte van het RAM ingesteld om de lengte van de interne teller van de PWM-module te volgen. De gegevens uitvoer van de ROM bestuurt de duty_cycle ingang naar de PWM-module. De waarde op de duty_cycle signaal zal een sinusgolfpatroon weergeven wanneer we de RAM-slots een voor een 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
  );

Het ROM van de sinusgolf simuleren

De afbeelding hieronder toont de golfvorm van de simulatie van de bovenste module in ModelSim. Ik heb de presentatie van de niet-ondertekende duty_cycle gewijzigd signaal naar een analoog formaat zodat we de sinusgolf kunnen waarnemen.

Het is de led_5 uitgang op het hoogste niveau die het PWM-signaal draagt, dat de externe LED bestuurt. We kunnen zien dat de output snel verandert wanneer de duty cycle stijgt of daalt. Maar wanneer de duty cycle bovenaan de sinusgolf staat, led_5 is een constante '1'. Als de golf zich onderaan de helling bevindt, blijft de output even op '0' staan.

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

Implementeren van LED-ademhaling op de FPGA

Ik heb de Lattice iCEcube2-software gebruikt om het ontwerp op het iCEstick FPGA-bord te implementeren. Gebruik het formulier hierboven om het project te downloaden en uit te proberen op je bord als je een iCEstick hebt!

De onderstaande lijst toont het gebruik van bronnen, zoals gerapporteerd door de Synplify Pro-software die bij iCEcube2 wordt geleverd. Het laat zien dat het ontwerp één blok RAM-primitief gebruikt. Dat is onze 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%)

Nadat u het ontwerp in iCEcube2 heeft gerouteerd, vindt u de .bin bestand in de led_breathing_Implmnt\sbt\outputs\bitmap map in de Lattice_iCEcube2_proj projectmap.

U kunt de Lattice Diamond Programmer Standalone software gebruiken om de FPGA te programmeren, zoals weergegeven in de iCEstick gebruikershandleiding. Dat is wat ik deed, en de GIF-animatie hieronder laat het resultaat zien. De lichtintensiteit van de LED oscilleert met een sinusgolfpatroon. Het ziet er heel natuurlijk uit en de LED lijkt te "ademen" als je er wat fantasie in steekt.

Laatste gedachten

Het gebruik van blok-RAM voor het opslaan van vooraf berekende sinuswaarden is vrij eenvoudig. Maar er zijn een paar beperkingen die deze methode alleen geschikt maken voor sinusgolven met beperkte X- en Y-resoluties.

De eerste reden die in me opkomt, is de 32-bits limiet voor gehele waarden die ik eerder heb besproken. Ik weet zeker dat je dit probleem kunt oplossen door de sinuswaarde slimmer te berekenen, maar dat is niet alles.

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

Als het aantal bits dat wordt gebruikt voor de sinuswaarden van de Y-as de oorspronkelijke RAM-diepte overschrijdt, zal de synthesetool extra RAM's of LUT's gebruiken om de ROM te implementeren. Het zal meer van uw resourcebudget opslokken naarmate u de Y-precisie verhoogt.

In theorie hoeven we maar één kwadrant van de sinusgolf op te slaan. Daarom zou u weg kunnen komen met een kwart van het RAM-gebruik als u een finite-state machine (FSM) zou gebruiken om de ROM-uitlezing te regelen. Het zou het sinuskwadrant moeten omkeren voor alle vier de permutaties van de X- en Y-assen. Dan zou je een volledige sinusgolf kunnen bouwen van het enkele kwadrant dat is opgeslagen in blok RAM.

Helaas is het moeilijk om alle vier de segmenten soepel te laten aansluiten. Twee gelijke samples in de gewrichten aan de boven- en onderkant van de sinusgolf vervormen de gegevens door vlakke plekken op de sinusgolf te creëren. Het introduceren van ruis gaat voorbij aan het doel om alleen het kwadrant op te slaan om de precisie van de sinusgolf te verbeteren.


VHDL

  1. Een CloudFormation-sjabloon maken met AWS
  2. Hoe maak je een wrijvingsloze UX
  3. Een lijst met strings maken in VHDL
  4. Hoe maak je een Tcl-gestuurde testbench voor een VHDL-codeslotmodule?
  5. Een PWM-controller maken in VHDL
  6. Hoe RAM vanuit een bestand te initialiseren met TEXTIO
  7. Hoe maak je een zelfcontrolerende testbank aan
  8. Een gekoppelde lijst maken in VHDL
  9. Hoe een array van objecten in Java te maken
  10. Hoe medische professionals digitale productie gebruiken om anatomische modellen van de volgende generatie te maken
  11. Een functieblok aanroepen vanuit een OPC UA-client met behulp van een informatiemodel