|
Table of Contents
|
Bevezető
Az FPGA processzor
Az FPGA alapvetően különbözik a többi mikrovezérlőtől (pl. ESP32) olyan szempontból, hogy még a többi az utasításokat szekvenciálisan hajtják végre, addig az FPGA átalakítja a belső szerkezetét, és elektronikai szinten, párhuzamosan történnek a folyamatok. Ezt a megközelítést kellően izgalmasnak találtam ahhoz, hogy rászánjak 3 beteg napot a megismerésére, és leírjam a tanultakat. A 3 napból már látható, hogy ennek a programozása nem lesz egyszerű.
A lentiekben nagyon sokat segített a Gemini, a kód egy részét is ez a nyelvi modell generálta.
A LILYGO mikrovezérlő
A LILYGO-nak van egy olyan terméke, amin ESP32 és FPGA is található; ezzel kísérleteztem. Erről a termékről van szó: https://lilygo.cc/products/t-fpga. Ezen az oldalon vásároltam meg: https://www.banggood.com/hu/LILYGO-T-FPGA-ESP32-S3-Development-Board-M_2-Slot-FPGA-GW1NSR-LV4CQN48PC6-or-I5-low-power-Microcontrollers-WiFi-Bluetooth5-Module-p-1990410.html. Az ára pár ezer forint, azaz megfizethető. Noha vásároltak kiegészítőket is, a lenti példák mindegyikét végre lehet hajtani egy szem LILYGO termékkel, amihez egy laptopra és két USB-C adatkábelre van csak szükség.
A mikrovezérlő részei
A mikrovezérlő részeit az alábbi ábra illusztrálja (a kép forrása: https://github.com/Xinyuan-LilyGO/T-FPGA, ahonnan bizonyos beállítási példák is származnak):
Számunkra fontos alkatrészek az alábbiak:
- Nyomógombok: a fenti ábrán alul találhatóak; balról jobbra haladva:
- ESP32 BOOT: valójában általános célú (a BOOT-nak probléma esetében van szerepe)
- ESP32 RESET: ESP32 újraindítás
- FPGA általános célú
- FPGA POWER
- LED-ek: a fenti ábrán a bal felső sarokban találhatóak, fentről lefelé:
- Zöld: power LED (ESP32-ről programból vezérelhető)
- Piros: FPGA-ból vezérelhető, általános célú
- Kék: ESP32-ből vezérelhető, általános célú
Tehát van egy-egy általános célú nyomógomb és egy-egy LED, belül tud kommunikálni a két egység, valamint az ESP32-nek van soros kimenete, mindezekkel már egész sokmindent ki lehet próbálni.
Helló, FPGA világ!
Tesztkód
A legegyszerűbb, Helló, világ! szintű példaprogram elkészítése is meglehetősen összetett feladat. A végeredmény egy LED villogó lesz. Ehhez a következőkre lesz szükség:
- Egy megfelelő ESP32 programra, ami a feszültséget biztosítja.
- Egy ESP32 fejlesztőkörnyezetre, aminek a segítségével fel tudjuk tölteni a kódot az eszközre.
- Egy FPGA kódra, ami villogtatja a LED-et.
- Egy FPGA fejlesztőkörnyezetre.
Összekapcsolás a számítógéppel
Az eszközön 2 darab USB-C bemenet van. A fenti ábra alapján a bal oldali tartozik az ESP32-höz, a jobb oldali pedig az FPGA-hoz. Ahhoz, hogy fel tudjuk tölteni az adatokat, egy-egy adatkábelre van szükségünk. Olcsóbb eszközökhöz gyakran adnak olyan kábelt, amivel csak tölteni lehet, emiatt ha a feltöltés nem működik, próbálkozzunk másik, adott esetben drágább kábellel.
Kössük össze mindkét USB-C portot egy-egy USB-vel a számítógépen.
Az Arduino IDE beállítása
Az Arduino IDE-t fogjuk használni. A következőket hajtsuk végre:
- Töltsük le a telepítőt a https://www.arduino.cc/en/software oldalról.
- Telepítsük fel.
- File → Preferences… → Additional board manager URLs: https://espressif.github.io/arduino-esp32/package_esp32_index.json (ezt akkor is hajtsuk végre, ha már van feltelepítve ESP32) → OK
- Tools → Board → Boards Manager… → a keresőbe írjuk be: esp32 → válasszuk ki ezt: esp32 (by Expressif System) → a verzió legyen 2.0.17 (én ezzel próbáltam úgy, hogy már volt 3-as verzió)
- Tools → esp32 → ESP32S3 Dev Module
- Tools → Port → az egyetlen lehetőséget válasszuk ki (pl. COM3)
- Tools → USB CDC On Boot: Enabled
- Tools → Flash Mode: QIO 80MHz
- Tools → Flash Size: 16MB
- Tools → Partition Scheme: 16M Flash (3MB APP/9.9MB FATFS)
- Sketch → Include Library → Manage Libraries… → a keresőbe: XPowersLib (by Lewis He) → telepítsük fel (a 0.3.3-as verziót telepítettem)
Az ESP32 kódja
Alkalmazás létrehozása:
- File → New Sketch
- Létrejön egy ino kiterjesztésű fájl, ami tkp. C++.
- Fontos, hogy a könyvtár neve és a kiterjesztés nélküli fájlnév megegyezzen.
Másoljuk be az alábbi kódot:
#include "Arduino.h" #include "Wire.h" #include "XPowersLib.h" //https://github.com/lewisxhe/XPowersLib #define PIN_IIC_SDA 38 #define PIN_IIC_SCL 39 XPowersAXP2101 PMU; void setup() { Serial.begin(115200); while (!Serial && millis() < 5000); delay(500); Serial.println("Hello T-FPGA-CORE"); bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_IIC_SDA, PIN_IIC_SCL); if (result == false) { Serial.println("PMU is not online..."); while (1) delay(50); } PMU.setDC4Voltage(1200); // Here is the FPGA core voltage. Careful review of the manual is required before modification. PMU.setALDO1Voltage(3300); // BANK0 area voltage PMU.setALDO2Voltage(3300); // BANK1 area voltage PMU.setALDO3Voltage(2500); // BANK2 area voltage PMU.setALDO4Voltage(1800); // BANK3 area voltage PMU.enableALDO1(); PMU.enableALDO2(); PMU.enableALDO3(); PMU.enableALDO4(); } void loop() { delay(10); }
Kattintsunk az Upload gombra (előzetesen már össze van kötve a számítógéppel.)
Majd a jobb felső sarokban kattintsunk a Serial Monitorra. A megnyíló Serial Monitoron válasszuk ki a 115200 Baud Rate-et. Ha minden rendben történt, a következőt látjuk:
Hello T-FPGA-CORENekünk most a PMU részek az érdekesek, azok adják az áramot az FPGA-nak, ami lehetővé teszi a program feltöltését is.
A Gowin alkalmazás
Az FPGA integrált fejlesztőkörnyezete a Gowin alkalmazás. A telepítése nem egyszerű.
- Telepítés
- Nyissuk meg ezt az oldalt: https://www.gowinsemi.com/en/support/download_eda/.
- Regisztráljunk, majd lépjünk be.
- Belépés után ismét nyissuk meg a https://www.gowinsemi.com/en/support/download_eda/ oldalt.
- Töltsük le a megfelelő Gowin alkalmazást (én ezt töltöttem le: Gowin V1.9.12.01 (Windows x64)).
- Indítsuk el az exe-t, és telepítsük fel az alkalmazást.
- Telepítsünk fel minden kiegészítőt, amit felajánl.
- Indítás
- Első indításkor kéri a licenc fájlt.
- A megfelelő nyomógombra kattintva a https://www.gowinsemi.com/en/support/license/ oldalra jutunk. Itt lehet igényelni licenc fájlt.
- Meg kell adni a cégnevet. Én ezt adtam meg: Csaba Farago Sole Proprietor (Faragó Csaba egyéni vállalkozó) és elfogadták.
- Kell a MAC cím. Windows alatt: ipconfig, ls a wifi physical address MAC címét kell megadni, ahol az internet szolgáltató neve szerepel.
- A licenc fájlt e-mailben küldik, nem automatikusan, így arra nagyjából egy napot várni kell.
- A licenc fájl egy évig érvényes.
- Másoljuk be egy tetszőleges könyvtárba, és az indításkor adjuk meg az elérési útvonalát.
Az FPGA kódja
Az FPGA kódját Verilogben írjuk. Lépések:
- Projekt létrehozása
- File → New… → Projects → FPGA Design Project → adjunk neki nevet → Next
- Series: GW1NSR
- Device: GW1NSR-4C
- Package: QFN48P
- Speed: C6/I5
- Válasszuk ki az ekkor már egyértelműt → Next → Finish
- Fájl hozzáadása
- Jobb kattintás a Design fülön belül bárhol → New File → Verilog File
- A fájl neve legyen ez: top. A kiterjesztés automatikusan hozzáadja.
- Az Add to current projekt maradjon bepipálva → OK
- Nyissuk meg a top.v fájlt, és írjuk bele ezt és mentsük el:
module top(
output led
);
assign led = 1'b1;
endmodule- További beállítások
- A menüből Process → Configuration → Synthesize → General → Top Module/Entity: top → OK
- Menjünk át a Process fülre → kattintsunk duplán ezen: Synthesize. Elvileg zöld pipa kell, hogy megjelenjen.
- Kattintsunk duplán a User Constraints → FloorPlanner-en
- Első kattintáskor kiírja, hogy még nincs CSV fájl, készíteni szeretne egyet; hagyjuk jóvá (OK).
- Fölül válasszuk ki a Package View-t, bár ennek nincs nagy jelentősége, csak annyi, hogy lássuk, melyik port melyik csoporthoz tartozik.
- Alul válasszuk ki az I/O COntraint fület.
- Fent létrehoztuk a led változót, ami itt megjelenik. A Location oszlopba írjuk ezt be kézzel: 15. A többit hagyjuk alapértelmezett értékeken.
- A Package View-ban látjuk, hogy ez alul van.
- Fent kattintsunk a mentés ikonra.
- Menjünk vissza a főablakba (bezárhatjuk a FloorPlannert, de nem muszáj).
- A főablakban a Design fülön kattintsunk duplán ezen: Place & Route
- Pár másodperc elteltével mellette zöld pipa kell, hogy megjelenjen.
- Ezzel kész a fordítás.
- Feltöltés
- Fent kattintsunk a Programmer ikonra vagy válasszuk ki a Tools menüből.
- A Gowin Programmeren belül kattintsunk a Scan Devire-ra.
- Válasszuk ki a GW1NSR-4C-t, majd OK.
- Kattintsunk duplán a megjelenő soron.
- Az Access Mode maradjon SRAM Mode.
- Az Operation legyen SRAM Program.
- Alul megjelenik a File Name. Ott válasszuk ki az fs fájlt. Ezt megtalálni nem egyszerű, és kell a teljes elérési útvonal. Az elérésben segíthet ez: C:\Programs\Gowin\Gowin_V1.9.12.01_x64\IDE\bin\Documents\fpga_project\impl\pnr\fpga_project.fs.
- Megnyitás → Save
- Majd kattintsunk a Program/Configure ikonra.
- Kb. 2 másodperc alatt be kell, hogy következzen a feltöltés. Eredmény: világít a piros LED!
A későbbiekben a következő lépéseket kell megtenni:
- Kód módosítás után Synthesize.
- Ha változott a kiosztás, akkor a FloorPlanner-en belül Reload, az értékek beírása ill. módosítása, majd Save.
- A főprogramban Place & Route.
- A feltöltőben Program/Configure.
A fenti kód magyarázata:
- Létrehoztunk egy led output portot, amit beállítottunk 15-re; ez a piros LED portja az FPGA-n.
- Az assign led = 1'b1; sor 1-et rendel hozzá, ami bekapcsolja.
Próba: írjuk át 0-ra (assign led = 1'b0;); ez esetben kikapcsol a LED.
Problémamegoldás
A fejlesztés során sokszor előfordul, hogy az eszköz "beragad": nem működik és nem is lehet programot feltölteni. Ekkor a teendő:
- Húzzuk ki az USB-t.
- Nyomjuk le és tartsuk nyomva a BOOT gombot (amelyik a legközelebb van a közepéhez).
- Dugjuk rá az USB-t úgy, hogy nyomva tartjuk a BOOT gombot.
- Még mindig tartsuk nyomva a BOOT gombot.
- Nyomjuk meg röviden a RESET gombot (a BOOT mellettit).
- Engedjük el a BOOT gombot.
Alap építőkövek
Az ESP32 építőkövei
Az ESP32 ismertetése túlmutat ennek az oldalnak a keretein; részletesen olvashatunk róla a ESP32 oldalon. Az alábbi példakód a következőket tartalmazza:
- A soros üzenetküldő beállítása.
- A zöld LED felvillantása és lekapcsolása.
- A nyomógomb hatására a kék LED felvillanása, számláló növelése és soros portra történő írása.
- Mindeközben az FPGA áramellátásának biztosítása.
#include "Arduino.h" #include "Wire.h" #include "XPowersLib.h" #define BTN_PIN 0 #define PIN_IIC_SDA 38 #define PIN_IIC_SCL 39 #define PIN_LED 46 XPowersAXP2101 PMU; int counter; void setup() { Serial.begin(115200); while (!Serial && millis() < 5000); delay(500); Serial.println("Hello T-FPGA-CORE"); pinMode(BTN_PIN, INPUT_PULLUP); pinMode(PIN_LED, OUTPUT); bool result = PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_IIC_SDA, PIN_IIC_SCL); if (result == false) { Serial.println("PMU is not online..."); while (1) delay(50); } PMU.setDC4Voltage(1200); // Here is the FPGA core voltage. Careful review of the manual is required before modification. PMU.setALDO1Voltage(3300); // BANK0 area voltage PMU.setALDO2Voltage(3300); // BANK1 area voltage PMU.setALDO3Voltage(2500); // BANK2 area voltage PMU.setALDO4Voltage(1800); // BANK3 area voltage PMU.enableALDO1(); PMU.enableALDO2(); PMU.enableALDO3(); PMU.enableALDO4(); PMU.setChargingLedMode(XPOWERS_CHG_LED_ON); delay(1000); PMU.setChargingLedMode(XPOWERS_CHG_LED_OFF); counter = 0; } void loop() { digitalWrite(PIN_LED, LOW); if (digitalRead(BTN_PIN) == LOW) { digitalWrite(PIN_LED, HIGH); counter++; Serial.println(counter); while(digitalRead(BTN_PIN) == LOW) { delay(10); } } delay(10); }
Az FPGA programok általános részei
Az FPGA programok feltöltésekor is szükség van megfelelő programra az ESP32-n. A bevezetőben bemutatott alkalmazást használhatjuk erre a célra, vagy az imént bemutaottt is megfelelő.
Az alábbi táblázat megmutatja, hogy melyik lábat hogyan kell beállítani a FloorPlanner-en belül (az IO Type-ot is egy kivétgelével át kell állítani):
| Irány | Változónév | Pozíció | IO Type | Magyarázat |
|---|---|---|---|---|
| output | led | 15 | LVCOMS18 | a piros LED |
| input | key | 46 | LVCOMS33 | a második nyomógomb |
| input | clk | 45 | LVCOMS33 | 27MHz-es kristály órajel |
| input | cs | 39 | LVCOMS33 | Chip Select; ESP32 kommunikáció kezdeményezés |
| input | sck | 44 | LVCOMS33 | Serial Clock; ESP32 kommunikáció üteme |
| input | mosi | 40 | LVCOMS33 | Master Out Slave In; ESP32 → FPGA kommunikáció |
| output | miso | 41 | LVCOMS33 | Master In Slave Out; FPGA → ESP32 kommunikáció |
FPGA nyomógomb 1
Az alábbi kódban a LED és a nyomógomb fixen össze van kapcsolva:
module top(
input key,
output led
);
assign led = ~key;
endmoduleMivel a nyomógomb esetén a lenyomott állapot jelenti a 0-t, az ellentettjét rendeltük a LED-hez. Ezzel azt érjük el, hogy akkor világít a LED, ha le van nyomva a nyomógomb.
FPGA nyomógomb 2
Az alábbi programban ha megnyomjuk a nyomógombot, akkor a LED átkapcsol:
module top(
input key,
output reg led
);
always @(posedge key) begin
led <= !led;
end
endmoduleItt megjelenik a fő ciklus (always). A posedge azt jelenti, hogy akkor fut le, amikor negatívból átvált pozitívba. A key paraméter azt adja meg, hogy amikor a nyomógomb vált át, magyarán amikor elengedik (ahogy már említettem, a pozitív itt az elengedett állapot). Ekkor a led változó felveszi a tagadását.
Próba: írjuk át a posedge-et arra, hogy negedge. Ebben az esetben a pozitívból negatívba történő váltásnál jelenik meg a változás, azaz a nyomógomb lenyomásánál.
FPGA időzítés
Ahogy arról már volt szó, az FPGA-t egy 27 MHz-es kristály órajel vezérli. Tekintsük a következő programot:
module top(
input clk,
output led
);
reg [23:0] counter;
always @(posedge clk) begin
counter <= counter + 1'b1;
end
assign led = counter[23];
endmoduleA clk itt az órajel, ami tehát másodpercenként 27 milliószor lefut. Azaz a posedge belseje ilyen gyakran fut le. Itt egy 24 bites számlálót növelünk. A LED a számláló legnagyobb helyiértékű bitjével van fixen összekapcsolva (assign led = counter[23];). Emlékeztető: az FPGA-ban minden egyszerre történik: ez a hozzárendelés fixen be van drótozva, miközben az always főciklus folyamatosan "ketyeg". Egy teljes ciklus kb. 0.62 másodperc alatt fut le, azaz 0.31 másodpercig világít és ugyanennyi ideig alszik ki a LED.
ESP32 → FPGA kommunikáció
Az alábbi példában az ESP32 "szól oda" az FPGA-nak: az ESP32 nyomógombjának a lenyomásával vezéreljük az FPGA LED-et. Ebben az a különleges, hogy az FPGA Reset lábat HIGH-on kell tartani, hogy fusson, ami viszont a 46-os láb, azaz a LED. Magyarán a kék LED folyamatosan világítani fog.
Ez az ESP32 kód:
#include "Arduino.h" #include "SPI.h" #include "Wire.h" #include "XPowersLib.h" #define PIN_BTN 0 #define PIN_FPGA_CS 1 #define PIN_FPGA_SCK 2 #define PIN_FPGA_MOSI 3 #define PIN_FPGA_MISO 5 #define PIN_FPGA_RST 46 // ez a LED is #define PIN_IIC_SDA 38 #define PIN_IIC_SCL 39 XPowersAXP2101 PMU; void setup() { Serial.begin(115200); Wire.begin(PIN_IIC_SDA, PIN_IIC_SCL); PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_IIC_SDA, PIN_IIC_SCL); PMU.setDC4Voltage(1200); PMU.setALDO1Voltage(3300); PMU.setALDO2Voltage(3300); PMU.enableALDO1(); PMU.enableALDO2(); // Reset láb kezelése: HIGH-on kell tartani, hogy az FPGA fusson! pinMode(PIN_FPGA_RST, OUTPUT); digitalWrite(PIN_FPGA_RST, HIGH); pinMode(PIN_BTN, INPUT_PULLUP); pinMode(PIN_FPGA_CS, OUTPUT); digitalWrite(PIN_FPGA_CS, HIGH); // SPI indítása SPI.begin(PIN_FPGA_SCK, PIN_FPGA_MISO, PIN_FPGA_MOSI); Serial.println("ESP32 SPI Kész. Nyomd meg a gombot!"); } void loop() { byte dataToSend = (digitalRead(PIN_BTN) == LOW) ? 0xFF : 0x00; digitalWrite(PIN_FPGA_CS, LOW); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); SPI.transfer(dataToSend); SPI.endTransaction(); digitalWrite(PIN_FPGA_CS, HIGH); delay(50); }
A lényegi rész a loop()-on belül van, azon belül is a transfer() hívás.
A fogadó oldal az alábbi:
module top(
input cs,
input sck,
input mosi,
output led
);
reg [7:0] shift_reg;
reg [7:0] received_data;
reg [2:0] bit_cnt;
always @(posedge sck or posedge cs) begin
if (cs) begin
bit_cnt <= 0;
end else begin
shift_reg <= {shift_reg[6:0], mosi};
if (bit_cnt == 7)
received_data <= {shift_reg[6:0], mosi};
bit_cnt <= bit_cnt + 1;
end
end
assign led = (received_data == 8'hFF) ? 1'b1 : 1'b0;
endmoduleLáthatjuk, hogy a fogadás nem egyszerű; lényegében egyetlen bitnyi információ is ilyen nehézkesen megy át lapkán belül. De ha minden rendben történt, és lenyomjuk az ESP32 nyomógombját, akkor világít az FPGA LED-je.
FPGA → ESP32 kommunikáció
A következő programban az FPGA nyomógombbal vezéreljük az ESP32 LED-jét. Mivel a 46-os lábat, azaz a kék LED-et folyamatosan magas szinten kell tartanunk, a zöld ledet fogjuk változtatni. Az ESP32 kód az alábbi:
#include "Arduino.h" #include "SPI.h" #include "Wire.h" #include "XPowersLib.h" #define PIN_FPGA_CS 1 #define PIN_FPGA_SCK 2 #define PIN_FPGA_MOSI 3 #define PIN_FPGA_MISO 5 #define PIN_FPGA_RST 46 // nem használhatjuk LED-ként, HIGH-on kell tartanunk #define PIN_IIC_SDA 38 #define PIN_IIC_SCL 39 XPowersAXP2101 PMU; void setup() { Serial.begin(115200); // PMU indítása Wire.begin(PIN_IIC_SDA, PIN_IIC_SCL); PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, PIN_IIC_SDA, PIN_IIC_SCL); PMU.setDC4Voltage(1200); PMU.enableALDO1(); PMU.enableALDO2(); // FPGA RESET magasan tartása pinMode(PIN_FPGA_RST, OUTPUT); digitalWrite(PIN_FPGA_RST, HIGH); pinMode(PIN_FPGA_CS, OUTPUT); digitalWrite(PIN_FPGA_CS, HIGH); SPI.begin(PIN_FPGA_SCK, PIN_FPGA_MISO, PIN_FPGA_MOSI); } void loop() { // Küldünk egy dummy bájtot, hogy megkapjuk a választ digitalWrite(PIN_FPGA_CS, LOW); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); byte received = SPI.transfer(0x00); SPI.endTransaction(); digitalWrite(PIN_FPGA_CS, HIGH); if (received == 0xFF) { PMU.setChargingLedMode(XPOWERS_CHG_LED_ON); } else { PMU.setChargingLedMode(XPOWERS_CHG_LED_OFF); } delay(100); }
Az FPGA pedig a következő:
module top(
input key,
input cs,
input sck,
output miso
);
reg [7:0] txd_shift_reg = 8'h00;
always @(negedge sck or posedge cs) begin
if (cs) begin
txd_shift_reg <= (key == 1'b0) ? 8'hFF : 8'h00;
end else begin
txd_shift_reg <= {txd_shift_reg[6:0], 1'b0};
end
end
assign miso = txd_shift_reg[7];
endmoduleItt az FPGA nyomógomb lenyomásával villan fel a zöld LED.
Összeadó
Rakjuk össze az eddigieket egy egyszerű alkalmazásba: az ESP32 küldjön 2 egész számot az FPGA-nak, ami térjen vissza azok összegével. Az eredményt az ESP32 írja a Serial portra.
Az ESP32 kód a következő:
#include "Arduino.h" #include "SPI.h" #include "Wire.h" #include "XPowersLib.h" #define PIN_FPGA_CS 1 #define PIN_FPGA_SCK 2 #define PIN_FPGA_MOSI 3 #define PIN_FPGA_MISO 5 #define PIN_FPGA_RST 46 XPowersAXP2101 PMU; void setup() { Serial.begin(115200); Wire.begin(38, 39); PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, 38, 39); PMU.setDC4Voltage(1200); PMU.enableALDO1(); PMU.enableALDO2(); pinMode(PIN_FPGA_RST, OUTPUT); digitalWrite(PIN_FPGA_RST, HIGH); pinMode(PIN_FPGA_CS, OUTPUT); digitalWrite(PIN_FPGA_CS, HIGH); SPI.begin(PIN_FPGA_SCK, PIN_FPGA_MISO, PIN_FPGA_MOSI); Serial.println("--- FPGA Osszeado Teszt ---"); } void loop() { byte a = random(0, 100); byte b = random(0, 100); digitalWrite(PIN_FPGA_CS, LOW); SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); SPI.transfer(a); // 1. bájt: 'a' küldése SPI.transfer(b); // 2. bájt: 'b' küldése byte sum = SPI.transfer(0x00); // 3. bájt: eredmény kiolvasása SPI.endTransaction(); digitalWrite(PIN_FPGA_CS, HIGH); Serial.printf("Kerdes: %d + %d | FPGA valasza: %d\n", a, b, sum); if (sum == (byte)(a + b)) { Serial.println("Sikeres szamitas! ✓"); } else { Serial.println("Hiba a szamitasban! ✗"); } delay(2000); }
AZ FPGA kód a következő:
module top(
input cs,
input sck,
input mosi,
output miso
);
reg [7:0] val_a, val_b;
reg [7:0] sum_result;
reg [4:0] bit_cnt;
reg [7:0] tx_reg;
// Felfutó él: Adatok beolvasása (MOSI)
always @(posedge sck or posedge cs) begin
if (cs) begin
bit_cnt <= 0;
end else begin
if (bit_cnt < 8)
val_a <= {val_a[6:0], mosi};
else if (bit_cnt < 16)
val_b <= {val_b[6:0], mosi};
if (bit_cnt < 24)
bit_cnt <= bit_cnt + 1'b1;
end
end
// Lefutó él: Adatok kiküldése (MISO)
always @(negedge sck or posedge cs) begin
if (cs) begin
tx_reg <= 8'h00;
end else begin
// Amikor megérkezett a 16. bit (a 2. bájt vége), azonnal betöltjük az összeget
if (bit_cnt == 16) begin
// Az összeadást itt végezzük el, és rögtön toljuk is ki az első bitet
// A 'val_b' utolsó bitje már megérkezett a posedge-kor
tx_reg <= val_a + val_b;
end else if (bit_cnt > 16) begin
// A 3. bájt többi bitje alatt léptetünk
tx_reg <= {tx_reg[6:0], 1'b0};
end
end
end
// A MISO lábra mindig a regiszter 7. bitjét tesszük
assign miso = (bit_cnt >= 16) ? tx_reg[7] : 1'b0;
endmoduleItt a Serial Monitort kell figyelni.
Adatforgalom elemző
Az összeadó már kétirányú kommunikációt valósít meg; használjunk egy logikai elemzőt annak ellenőrzésére!
A logikai elemző
A logikai elemző egy hardvereszköz. Ennek neve USB Logic Analyzer (https://www.aliexpress.com/item/1005005388940883.html).
A következő módon kapcsoljuk a LILYGO-ra (a táblázatban megadom a később bemutatott PulseView alkalmazásbeli elnevezését is):
| Elemző | LILYGO láb | PulseView | Funkció |
|---|---|---|---|
| CND | GND | GND | Földelés |
| CH1 | 1 | D0 | CS (Chip Select) |
| CH2 | 2 | D1 | SCK (Órajel) |
| CH3 | 5 | D2 | MISO (FPGA → ESP32) |
| CH4 | 3 | D3 | MOSI (ESP32 → FPGA) |
A MISO/MOSI elnevezések ben a M a master (ami az ESP32), az S a slave (ami az FPGA):
- MISO = Master In, Slave Out: az információ a mesterbe megy be, a szolgából jön ki, tehát ekkor küld információt az FPGA az ESP32-nek.
- MOSI = Master Out, Slave In: az információ a mesterból ki, a szolgába be megy, tehát ekkor küld információt az ESP32 az FPGA-nak.
Az elemző másik végét a mellékelt USB kábellel kapcsoljuk a számítógéphez.
Szoftverek telepítése és beállítása
Meghajtó beállítása:
- Töltsük le a Zadig nevű szoftvert (https://zadig.akeo.ie/)
- Indítsuk le (nem kell telepíteni)
- Options → List All Devices
- A legördülő menüben válasszuk ezt ki: "Unknown Device".
- A bal oldalon nincs meghajtó.
- A jobb oldali mezőbe ez legyen kiválasztva (alapból ez van): WinUSB.
- Kattintsunk erre: Install Driver.
- Pár perc is lehet, míg feltelepíti.
Ha kihúzzuk, majd újra bedugás után nem működi, akkor Eszközkezelő → USB eszközök → Unknown device #1 → Eszköz eltávolítása. Majd húzzuk ki, és dugjuk vissza ismét. Várjuk meg, míg megtalálja ismét az ismeretlen eszközt, és kezdjük elölről a Zadiggal.
Elemző szoftver feltelepítése és beállítása:
- Töltsük le a PulseView alkalmazást a https://sigrok.org/wiki/Downloads oldalról (én ezt töltöttem le: Windows PulseView 0.4.2 (64bit)).
- Telepítsük fel és indítsuk el. (Alapból nem hoz létre ikont.)
- Fent középen van egy legördülő menü, ahol alapból Demo device szerepel. Kattintsunk rá.
- A felugró ablak első legördülő menüjéből válasszuk ki ezt: fx2lafw.
- Az Interface maradjon USB.
- Kattintsunk erre: Scan for devices using driver above.
- Megjelenik ez a listában: Device: Saleae Logic with 8 channels; válasszuk ki, majd OK.
Adjunk hozzá egy protokoll elemzőt:
- Kattintsunk a fenti sávban a sárga-zöld ikonra (Add protocol decoder)
- Keressünk rá arra, hogy SPI.
- Kattintsunk rá duplán.
- Megjelenik egy új sor SPI felirattal. Kattintsunk a feliratra.
- A 4 kiemelt adatot a fenti táblázat alapján állítsuk be, de itt is megadom: CLK (D1), MISO (D2), MOSI (D3), CS# (D0).
Elemzés végrehajtása
Az elemzéshez állítsunk be még pár dolgot:
- Felül van a mintavételezési frekvencia. Ezt állítsuk 24 MHz-re.
- A mintavételezések számát vegyük le alacsonyra, pl. ezerre (1k).
- Kattintsunk a D0-ra. Ott a Trigger legyen ez: Trigger on falling edge.
Ezekre amiatt van szükség, mert a teljes folyamat pár mikroszekundum alatt lefut, és 2 másodpercig nem történik semmi, azaz nagyon nehéz lenne pont eltalálni egy értelmes kommunikációt.
Majd felül kattintsunk a Run gombra. Ha minden jól ment, akkor hamarosan meg kell, hogy jelenjen az eredmény. Szükség esetén a + és a - gombokkal tudunk belenagyítani és kinagyítani, a görgetősávval görgetni. Például ha a mintavételezések számát egymillión hagyjuk, akkor bele kell nagyítani ahhoz, hogy lássunk is valamit.
Itt látható egy lefutás:
- A D0 az, ami kiváltotta a folyamatot (Chip Select).
- A D1 az órajel.
- A D3 az ESP32 → FPGA kommunikáció, amikor az ESP32 elküldi a 2 számot. Ezek a példában hexadecimálisan 3C és 33, azaz decimálisan 60 és 51.
- A D2 az FPGA → ESP32 kommunikáció, amikor az FPGA visszaküldi a választ. Ez a példában hexadecimálisan a 6F, ami decimálisan a 111.
Figyeljük meg a jelalakot, hogy fizikailag hogyan történik a kommunikáció. Valamint az adatátvitel sebességéről is képet kaphatunk, ha megnézzük fent az időbélyegzőket. Itt a teljes folyamat nettó ideje kb. 30 µs, a bruttó kb. 40 µs.
A fenti folyamat eredménye a Serial monitoron a következőképpen jelenik meg:
Kerdes: 60 + 51 | FPGA valasza: 111
Sikeres szamitas! ✓Egy összetettebb feladat
A feladat
Emeljük a tétet: megvalósítunk egy picit összetettebb feladatot, ahol ki tudjuk mérni az FPGA előnyét az ESP32-vel szemben: számoljuk meg, hogy hány olyan szám van 0-tól egymilliárdig, ami nem osztható sem 3-mal, sem 5-tel. Ezt nem zárt képlettel valósítjuk meg, hanem ciklussal, és egyesével megszámoljuk.
ESP32 megvalósítás
Az első ESP32-es megvalósítás az alábbi:
#include "Arduino.h" #include "Wire.h" void setup() { Serial.begin(115200); while (!Serial && millis() < 5000); delay(500); Serial.println("--- ESP32 Benchmark Indul (1 milliárd iteráció) ---"); uint32_t start_time = millis(); uint32_t result = 0; for (uint32_t i = 0; i <= 1000000000; i++) { if (i % 3 != 0 && i % 5 != 0) { result++; } } uint32_t end_time = millis(); float total_seconds = (end_time - start_time) / 1000.0; Serial.println("--- Benchmark Kész ---"); Serial.print("Eredmény: "); Serial.println(result); Serial.print("Eltelt idő: "); Serial.print(total_seconds); Serial.println(" másodperc"); } void loop() { delay(1000); }
Az eredmény: 533333333, a futásidő 83.66 másodperc.
Ez tartalmaz milliárdnyi osztást, ami nem hatékony. Kicsit optimalizáljunk:
#include "Arduino.h" #include "Wire.h" void setup() { Serial.begin(115200); while (!Serial && millis() < 5000); delay(500); Serial.println("--- ESP32 Benchmark Indul (1 milliárd iteráció) ---"); uint32_t start_time = millis(); uint32_t result = 0; int a = 0; int b = 0; for (uint32_t i = 0; i <= 1000000000; i++) { if (a != 0 && b != 0) { result++; } a++; if (a == 3) { a = 0; } b++; if (b == 5) { b = 0; } } uint32_t end_time = millis(); float total_seconds = (end_time - start_time) / 1000.0; Serial.println("--- Benchmark Kész ---"); Serial.print("Eredmény: "); Serial.println(result); Serial.print("Eltelt idő: "); Serial.print(total_seconds); Serial.println(" másodperc"); } void loop() { delay(1000); }
Ez is jól működik, és a futásidő lecsökkent 67.99 másodpercre. Ez utóbbi algoritmust valósítjuk meg az FPGA-n, tehát a versenytárs ez utóbbi eredmény lesz.
Alap FPGA megvalósítás
Az ESP32 kód ez:
#include "Arduino.h" #include "SPI.h" #include "Wire.h" #include "XPowersLib.h" #define PIN_FPGA_CS 1 #define PIN_FPGA_SCK 2 #define PIN_FPGA_MOSI 3 #define PIN_FPGA_MISO 5 #define PIN_FPGA_RST 46 XPowersAXP2101 PMU; void setup() { Serial.begin(115200); Wire.begin(38, 39); PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, 38, 39); PMU.setDC4Voltage(1200); PMU.enableALDO1(); PMU.enableALDO2(); digitalWrite(PIN_FPGA_RST, HIGH); pinMode(PIN_FPGA_CS, OUTPUT); digitalWrite(PIN_FPGA_CS, HIGH); SPI.begin(PIN_FPGA_SCK, PIN_FPGA_MISO, PIN_FPGA_MOSI); delay(1000); Serial.println("--- FPGA Benchmark Indul ---"); digitalWrite(PIN_FPGA_CS, LOW); SPI.transfer(0x01); digitalWrite(PIN_FPGA_CS, HIGH); delay(100); // Adunk időt az FPGA-nak az átállásra uint32_t startTime = millis(); Serial.print("FPGA dolgozik"); while(true) { digitalWrite(PIN_FPGA_CS, LOW); byte status = SPI.transfer(0x00); digitalWrite(PIN_FPGA_CS, HIGH); // Ha 0xA5-öt kapunk, akkor még RUN állapotban van if (status != 0xA5) { // Megnézzük még egyszer, biztos ami biztos delay(10); digitalWrite(PIN_FPGA_CS, LOW); status = SPI.transfer(0x00); digitalWrite(PIN_FPGA_CS, HIGH); if (status != 0xA5) break; } if (millis() % 1000 < 50) Serial.print("."); delay(100); } uint32_t endTime = millis(); Serial.println(" KÉSZ!"); // Eredmény kiolvasása uint32_t result = 0; digitalWrite(PIN_FPGA_CS, LOW); result |= (uint32_t)SPI.transfer(0x00) << 24; result |= (uint32_t)SPI.transfer(0x00) << 16; result |= (uint32_t)SPI.transfer(0x00) << 8; result |= (uint32_t)SPI.transfer(0x00); digitalWrite(PIN_FPGA_CS, HIGH); Serial.printf("Eredmény: %u\n", result); Serial.printf("Eltelt idő: %.3f másodperc\n", (endTime - startTime) / 1000.0); } void loop() { delay(10); }
Az FPGA kód pedig ez:
module top(
input clk, // 45
input cs, // 39
input sck, // 44
input mosi, // 41
output miso, // 40
output led // 15
);
// Állapotgép
localparam IDLE = 2'd0, RUN = 2'd1, DONE = 2'd2;
reg [1:0] state = IDLE;
reg [31:0] i, result, final_result;
reg [1:0] cnt3;
reg [2:0] cnt5;
// SPI Szinkronizáció (27MHz-es mintavételezés)
reg [2:0] sck_sync, cs_sync, mosi_sync;
always @(posedge clk) begin
sck_sync <= {sck_sync[1:0], sck};
cs_sync <= {cs_sync[1:0], cs};
mosi_sync <= {mosi_sync[1:0], mosi};
end
wire sck_rising = (sck_sync[2:1] == 2'b01);
wire sck_falling = (sck_sync[2:1] == 2'b10);
wire cs_active = ~cs_sync[1];
reg [4:0] bit_cnt;
reg [7:0] rx_reg;
reg [7:0] tx_reg;
reg [2:0] byte_cnt;
// Számítási logika (27MHz)
always @(posedge clk) begin
case (state)
IDLE: begin
if (cs_active && bit_cnt == 8 && rx_reg == 8'h01) begin
i <= 1; result <= 0; cnt3 <= 1; cnt5 <= 1;
state <= RUN;
end
end
RUN: begin
if (cnt3 != 0 && cnt5 != 0) result <= result + 1'b1;
cnt3 <= (cnt3 == 2) ? 0 : cnt3 + 1'b1;
cnt5 <= (cnt5 == 4) ? 0 : cnt5 + 1'b1;
if (i == 1000000000) begin
final_result <= result;
state <= DONE;
end else i <= i + 1'b1;
end
DONE: if (cs_active && bit_cnt == 8 && rx_reg == 8'h02) state <= IDLE;
endcase
end
// SPI Bit kezelés
always @(posedge clk) begin
if (~cs_active) begin
bit_cnt <= 0;
byte_cnt <= 0;
tx_reg <= (state == RUN) ? 8'hA5 : (state == DONE ? final_result[31:24] : 8'h00);
end else begin
if (sck_rising) begin
rx_reg <= {rx_reg[6:0], mosi_sync[1]};
bit_cnt <= bit_cnt + 1'b1;
end
if (sck_falling) begin
if (bit_cnt == 8) begin
bit_cnt <= 0;
byte_cnt <= byte_cnt + 1'b1;
case (byte_cnt)
0: tx_reg <= (state == DONE) ? final_result[23:16] : 8'hA5;
1: tx_reg <= (state == DONE) ? final_result[15:8] : 8'hA5;
2: tx_reg <= (state == DONE) ? final_result[7:0] : 8'hA5;
default: tx_reg <= 8'h00;
endcase
end else begin
tx_reg <= {tx_reg[6:0], 1'b0};
end
end
end
end
assign miso = cs_active ? tx_reg[7] : 1'bz;
assign led = (state == RUN); // Piros LED jelzi a futást
endmoduleMiután feltöltöttük az FPGA kódot, nyomjuk meg az ESP32 RESET-et (ha alul vannak a gombok, akkor balról a második), majd figyeljük a Serial monitort.
A futásidő nálam 37.01 másodperc volt, tehát sikerült javítani az ESP32-höz képest.
Optimalizált FPGA megvalósítás
Az FPGA ereje a párhuzamosításban van. A következő változatban egyszerre vizsgálunk 4 számot. Az ESP32 kód marad, az FPGA kód a következőre változik:
module top(
input clk, // 45-ös láb, a 27MHz-es kristály
input cs, // 39-es láb (ESP32-S3 CS)
input sck, // 44-es láb (ESP32-S3 SCK)
input mosi, // 41-es láb (ESP32-S3 MOSI)
output miso, // 40-es láb (ESP32-S3 MISO)
output led // 15-ös láb (Piros LED)
);
// Állapotgép állapotai
localparam IDLE = 2'd0;
localparam RUN = 2'd1;
localparam DONE = 2'd2;
reg [1:0] state = IDLE;
reg [31:0] i;
reg [31:0] result;
reg [31:0] final_result;
// Modulo számlálók az aktuális 'i' értékhez
reg [1:0] c3;
reg [2:0] c5;
// --- PÁRHUZAMOS LOGIKA (4 szám vizsgálata egyszerre) ---
// Megvizsgáljuk: i, i+1, i+2, i+3
// Oszthatóság vizsgálata logikai úton (modulo művelet nélkül)
wire v0 = (c3 != 0) && (c5 != 0); // i
wire [1:0] c3_p1 = (c3 == 2) ? 2'd0 : c3 + 2'd1;
wire [2:0] c5_p1 = (c5 == 4) ? 3'd0 : c5 + 3'd1;
wire v1 = (c3_p1 != 0) && (c5_p1 != 0); // i+1
wire [1:0] c3_p2 = (c3 == 1) ? 2'd0 : (c3 == 2) ? 2'd1 : 2'd2;
wire [2:0] c5_p2 = (c5 == 3) ? 3'd0 : (c5 == 4) ? 3'd1 : c5 + 3'd2;
wire v2 = (c3_p2 != 0) && (c5_p2 != 0); // i+2
wire v3 = (c3 != 0) && (c5 >= 2 ? c5 - 3'd2 : c5 + 3'd3) != 0; // i+3
// Összeadjuk a találatokat (0-4 között lehet)
// A {2'b0, vX} biztosítja, hogy ne legyen bit-levágás az összeadásnál
wire [2:0] batch_sum = {2'b0, v0} + {2'b0, v1} + {2'b0, v2} + {2'b0, v3};
// --- FŐ VEZÉRLŐ LOGIKA ---
always @(posedge clk) begin
case (state)
IDLE: begin
// Ha az ESP32-től megjön a 0x01 (START) parancs
if (cs_active && bit_cnt == 8 && rx_reg == 8'h01) begin
i <= 1;
result <= 0;
c3 <= 1; // 1-től indulunk
c5 <= 1;
state <= RUN;
end
end
RUN: begin
result <= result + batch_sum;
i <= i + 4;
// Modulo számlálók frissítése a következő körhöz (i+4)
// (i+4) mod 3 = (i+1) mod 3
case (c3)
2'd0: c3 <= 2'd1;
2'd1: c3 <= 2'd2;
2'd2: c3 <= 2'd0;
endcase
// (i+4) mod 5 = (i-1) mod 5
case (c5)
3'd0: c5 <= 3'd4;
3'd1: c5 <= 3'd0;
3'd2: c5 <= 3'd1;
3'd3: c5 <= 3'd2;
3'd4: c5 <= 3'd3;
endcase
if (i >= 1000000000) begin
final_result <= result;
state <= DONE;
end
end
DONE: begin
// Ha az ESP32-től megjön a 0x02 (RESET) parancs
if (cs_active && bit_cnt == 8 && rx_reg == 8'h02) begin
state <= IDLE;
end
end
endcase
end
// --- SPI SZINKRONIZÁLT INTERFÉSZ ---
reg [2:0] sck_sync, cs_sync;
always @(posedge clk) begin
sck_sync <= {sck_sync[1:0], sck};
cs_sync <= {cs_sync[1:0], cs};
end
wire sck_rising = (sck_sync[2:1] == 2'b01);
wire sck_falling = (sck_sync[2:1] == 2'b10);
wire cs_active = ~cs_sync[1];
reg [4:0] bit_cnt;
reg [7:0] rx_reg;
reg [7:0] tx_reg;
reg [2:0] byte_cnt;
// SPI Bit-kezelés
always @(posedge clk) begin
if (~cs_active) begin
bit_cnt <= 0;
byte_cnt <= 0;
// Ha a CS lemegy, betöltjük az első küldendő bájtot
if (state == RUN) tx_reg <= 8'hA5; // Busy jelzés
else if (state == DONE) tx_reg <= final_result[31:24]; // MSB
else tx_reg <= 8'h00;
end else begin
if (sck_rising) begin
rx_reg <= {rx_reg[6:0], mosi};
bit_cnt <= bit_cnt + 1'b1;
end
if (sck_falling) begin
if (bit_cnt == 8) begin
bit_cnt <= 0;
byte_cnt <= byte_cnt + 1'b1;
// Következő bájtok előkészítése
if (state == DONE) begin
case (byte_cnt)
0: tx_reg <= final_result[23:16];
1: tx_reg <= final_result[15:8];
2: tx_reg <= final_result[7:0];
default: tx_reg <= 8'h00;
endcase
end else tx_reg <= (state == RUN) ? 8'hA5 : 8'h00;
end else begin
tx_reg <= {tx_reg[6:0], 1'b0};
end
end
end
end
assign miso = cs_active ? tx_reg[7] : 1'bz;
assign led = (state == RUN); // Világít, amíg a számítás tart
endmoduleItt is indítsuk újra a RESET-tel és figyeljük a soros portot. A futásidő nálam 9.21 lett.
Tovább is lehetne optimalizálni, pl. még több párhuzamosítással vagy akár az órajel felgyorsításával; most eddig jutottunk.
8x8-as LED mátrix
Hozzákapcsoltam egy 8x8-as LED mátrixot, bár sajnos csak az ESP32-vel sikerült működésre bírnom. (Az igazán ütős az FPGA lett volna.)
A hardver
Ezt az eszközt vásároltam meg: https://www.banggood.com/hu/WS2812-LED-5050-RGB-8x8-64-LED-Matrix-Built-in-Full-Color-Driver-Light-Development-Board-for-Arduino-p-1990722.html. Ezen sajnos alapból nincs olyan láb, amire rá lehetne közvetlenül csatlakoztatni, ráadásul a forrasztás sem egyszerű. Hozzáforrasztottam egy 3 lábú tüskesort, és forró színes ragasztóval megragasztottam. (Bár a tapasztalatom szerint valójában a forrasztás is elegendő volt, és talán egyszerűbb lett volna pillanatragasztóval ragasztani.)
Kettő darab V+, IN, V- hármas van, de elég az egyiket használni. A bekötés:
- LED V- → LILYGO GND
- LED IN → LILYGO 2
- LED V+ → LILYGO 3.3V
Könyvtárak
Az alábbi példákhoz a következő könyvtárakat használjuk (Sketch → Include Library → Manage Libraries…):
- Adafruit NeoPixel
- Adafruit GFX Library
- Adafruit NeoMatrix
És a függőségek, amelyeket automatikusan feltelepít.
Ezek után kipróbálhatjuk az alábbi példaprogramokat.
Futófény
A LED mátrix "helló világa" a futófény, melnyek kódja a következő:
#include <Adafruit_NeoPixel.h> #define PIN 2 // Az IN láb ide csatlakozik #define NUMPIXELS 64 // 8x8 mátrix Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); void setup() { pixels.begin(); pixels.setBrightness(20); } void loop() { for(int i=0; i<NUMPIXELS; i++) { pixels.clear(); pixels.setPixelColor(i, pixels.Color(0, 150, 0)); // Zöld futófény pixels.show(); delay(50); } }
Ábra
Mivel a LED mátrix gyakorlatilag RGB színek mátrixa, egyszerű színes ábrákat is létre tudunk hozni a segítségével, pl.:
#include <Adafruit_NeoPixel.h> #define PIN 2 #define NUMPIXELS 64 Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); // Színkonstansok a te listád alapján #define RED pixels.Color(255, 0, 0) #define GRN pixels.Color(0, 255, 0) #define BLU pixels.Color(0, 0, 255) #define YEL pixels.Color(255, 255, 0) #define PUR pixels.Color(255, 0, 255) #define CYN pixels.Color(0, 255, 255) #define WHT pixels.Color(255, 255, 255) #define OFF pixels.Color(0, 0, 0) // 8x8-as ábra minden szín felhasználásával const uint32_t rainbowMonster[8][8] = { {OFF, OFF, RED, RED, RED, RED, OFF, OFF}, // Fej teteje {OFF, RED, YEL, YEL, YEL, YEL, RED, OFF}, // Sárga rész {RED, YEL, WHT, BLU, BLU, WHT, YEL, RED}, // Szemek (Fehér+Kék) {RED, YEL, YEL, YEL, YEL, YEL, YEL, RED}, // Sárga arc {GRN, GRN, GRN, GRN, GRN, GRN, GRN, GRN}, // Zöld sáv {CYN, CYN, PUR, CYN, CYN, PUR, CYN, CYN}, // Világoskék + Lila száj {BLU, BLU, BLU, BLU, BLU, BLU, BLU, BLU}, // Kék sáv {PUR, OFF, PUR, OFF, OFF, PUR, OFF, PUR} // Lila lábak }; void setup() { pixels.begin(); // Alacsony fényerő, hogy a színek ne égjenek ki (WHT vs YEL) pixels.setBrightness(15); pixels.clear(); for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { pixels.setPixelColor(y * 8 + x, rainbowMonster[y][x]); } } pixels.show(); } void loop() { // Statikus kép }
Életjáték
Az életjáték egy látványos projekt egy ilyen mátrixon. (Ez lehetett volna sokkal gyorsabb FPGA segítségével, a párhuzamosítás miatt.)
#include <Adafruit_NeoPixel.h> #define PIN 2 #define NUMPIXELS 64 #define WIDTH 8 #define HEIGHT 8 Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); bool grid[WIDTH][HEIGHT]; bool nextGrid[WIDTH][HEIGHT]; uint32_t currentLiveColor; // Ez tárolja az aktuális futam színét // Színpaletta a te listád alapján uint32_t colors[] = { pixels.Color(255, 0, 0), // Piros pixels.Color(0, 255, 0), // Zöld pixels.Color(0, 0, 255), // Kék pixels.Color(255, 255, 0), // Sárga pixels.Color(255, 0, 255), // Lila pixels.Color(0, 255, 255), // Világoskék (Cyan) pixels.Color(255, 255, 255) // Fehér }; int numColors = 7; void setup() { pixels.begin(); pixels.setBrightness(10); randomSeed(analogRead(0)); // Véletlenszám generátor inicializálása initGrid(); } void initGrid() { // Új szín kiválasztása a listából currentLiveColor = colors[random(numColors)]; // Tábla feltöltése véletlenszerű sejtekkel for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { grid[y][x] = random(100) < 35; // 35% esély az életre } } } int countNeighbors(int x, int y) { int neighbors = 0; for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { if (i == 0 && j == 0) continue; int nx = x + i; int ny = y + j; if (nx >= 0 && nx < WIDTH && ny >= 0 && ny < HEIGHT) { if (grid[ny][nx]) neighbors++; } } } return neighbors; } void loop() { bool changed = false; int aliveCount = 0; // 1. Megjelenítés a választott színnel for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { pixels.setPixelColor(y * WIDTH + x, grid[y][x] ? currentLiveColor : 0); if (grid[y][x]) aliveCount++; } } pixels.show(); delay(250); // 2. Új generáció számítása for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { int neighbors = countNeighbors(x, y); if (grid[y][x]) { nextGrid[y][x] = (neighbors == 2 || neighbors == 3); } else { nextGrid[y][x] = (neighbors == 3); } if (nextGrid[y][x] != grid[y][x]) changed = true; } } // 3. Ellenőrzés: ha kihaltak vagy megállt a fejlődés -> ÚJRAINDÍTÁS ÚJ SZÍNNEL if (!changed || aliveCount == 0) { delay(1000); // Rövid szünet a váltás előtt initGrid(); } else { for (int y = 0; y < HEIGHT; y++) { for (int x = 0; x < WIDTH; x++) { grid[y][x] = nextGrid[y][x]; } } } }
Futó szöveg
Ugyancsak gyakori látványeleme a mátrixoknak a futószöveg, melynek kódja az alábbi:
#include <Adafruit_GFX.h> #include <Adafruit_NeoMatrix.h> #include <Adafruit_NeoPixel.h> #include "Wire.h" #include "XPowersLib.h" // APMU kezeléshez #define PIN 2 // Az adatláb (IN) XPowersAXP2101 PMU; // Mátrix konfigurálása (8x8, bal felső kezdőpont, sorfolytonos elrendezés) Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(8, 8, PIN, NEO_MATRIX_TOP + NEO_MATRIX_LEFT + NEO_MATRIX_ROWS + NEO_MATRIX_PROGRESSIVE, NEO_GRB + NEO_KHZ800); const uint32_t colors[] = { matrix.Color(255, 0, 0), matrix.Color(0, 255, 0), matrix.Color(0, 0, 255) }; int x = matrix.width(); int pass = 0; void setup() { // 1. PMU Inicializálása (FPGA bankok és táp bekapcsolása) Wire.begin(38, 39); if (PMU.begin(Wire, AXP2101_SLAVE_ADDRESS, 38, 39)) { PMU.setALDO1Voltage(3300); // Bank 0 PMU.enableALDO1(); } // 2. Mátrix inicializálása matrix.begin(); matrix.setTextWrap(false); matrix.setBrightness(15); matrix.setTextColor(colors[0]); } void loop() { matrix.fillScreen(0); // Képernyő törlése matrix.setCursor(x, 0); // Kurzornak az aktuális X pozícióba állítása matrix.print(F("Hello!")); // Ha a szöveg kifutott a bal oldalon, kezdjük újra jobbról // A "Hello!" 6 karakter, egy karakter kb. 6 pixel széles if(--x < -36) { x = matrix.width(); if(++pass >= 3) pass = 0; matrix.setTextColor(colors[pass]); // Színváltás minden körben } matrix.show(); delay(100); // Sebesség szabályozása }
Utószó
Napokig eltartott, mire sikerült működésre bírni és egy-két egyszerű alkalmazást elkészíteni. Rengeteg a beállítási lehetőség, nagyon könnyű hibázni és beragadni. A technológia ígéretes, de még mikroelektronika mércével mérve is sajnos még nem eléggé programozóbarát.






