FPGA

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

T-FPGA.jpg

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-CORE

Nekü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
  • 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;

endmodule

Mivel 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

endmodule

Itt 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]; 

endmodule

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

endmodule

Lá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];

endmodule

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

endmodule

Itt 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:

analyzer.jpg
  • 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

endmodule

Miutá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

endmodule

Itt 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.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License