OLED kijelző (SSD1306, 0.96″ ATtiny85-el is)

Tartalom:

  • Az OLED kijelző legfontosabb tulajdonságai
  • Használathoz szükséges programkönyvtárak letöltése, függvények
  • Használat ATtiny85 alaplappal, memória gondok, és megoldásuk
  • A kijelző vezérlése programkönyvtárak nélkül, részletes működés, elemi kijelző parancsok és kijelző memória írási módok
  • Saját függvények és karakterek sokkal kisebb program mérettel, OLED ATtiny85 alaplappal

——————————————————————-

A csapadékmérő műszeremet készítgetem. Még régebben vettem egy OLED kijelzőt, ami feltétlenül szeretnék felhasználni erre a feladatra. A kijelző 0,96 col átmérőjű, és 128×64 pont felbontású. A mérete az én szememhez nagyon szerény, a szabvány 8×5 pontból felépített karaktereket még szemüveggel sem látom. Azonban nagyon kontrasztos, fekete alapon fehér színnel ír, ami jól olvasható, nem kell háttérvilágítás stb. Mint hamarosan kiderült, primitív módon, de egyszerűen lehet nagyítani a karaktereket, így mégiscsak jól használhatónak bizonyult. Találtam itthon egy ATtiny85 alaplapot. Ahhoz is megpróbáltam „hozzáilleszteni” ezt a kijelzőt. Pontosabban olyan kicsi programot készíteni, ami elfér az ATtiny85 összesen 6 Kbyte programmemóriájában. Kezdeti sikertelenség után végül megismertem a kijelző illetve az azzal egybeépített SSD1306 chip részletes működését, és elemi parancsokkal elkészült egy olyan program, ami képes szövegeket és számokat megjeleníteni különböző méretben, és még érdemi programoknak is maradt hely a memóriában. Erről a leírás végén találsz részeteket! De első körben maradjunk a klasszikus Arduino UNO vagy nano felhasználásnál!

FONTOS!!! Ismereteim szerint nem végtelen a kijelző élettartama. Szerves anyagból készült LED világít, ami szép lassan elveszíti a fényerejét és megsemmisül. Nem pontosan értem mi történik, de egy TV szereléssel foglalkozó ismerősöm is megerősítette, hogy az OLED kijelzők folyamatos használat esetén 5 év után kidobhatók. Pláne ez a kis 500Ft-os vacak. Így eleve úgy tervezem, hogy az értékek kijelzése gombnyomással indul majd, sorban megmutat minden adatot és kikapcsol. Tehát egy plusz nyomógomb is kelleni fog, de ez túlélhető költség!

Számos videó és ismertető van a neten a használathoz. Ebből indultam ki, és az Arduino IDE-vel letöltöttem gyorsan a szükséges „Adafruit SSD1306” könyvtárat. Figyelmeztetett, hogy még másra is szükség van, így azokat is telepítettem:

A példa programokban azonnal találtam szuper, minden képességet bemutató programot. Nekem a grafikus képességek (vonal, háromszög kör rajzolás stb.) nem kellenek, így alaposan megcsonkítottam a példa programot. Kizárólag a karakter megjelenítést hagytam benne.

Egy kicsit próbáltam utána olvasni a kijelző adatlapjában, hogyan is jelennek meg a karakterek. A blokkvázlatban nem láttam karaktergenerátort, így gyorsan kiderült, a programkönyvtár a kerekterek definícióit is tartalmazza, és amikor karaktert írok a képernyőre, akkor valójában pontonként „megrajzolja” a szükséges betűt. A karakterek adatai bizony elég sok memóriát foglalnak el! A legegyszerűbb program is irdatlan nagy. 15 Kbyte program memóriát zabál fel, és a RAM terület is alig marad. Valahol olvastam a lib működéséről, hogy a ram-ban lefoglal 1 kbyte-ot (128×64=8192 bit, és az pont 1 kbyte), abban hozza létre a megjelenítendő képet, és egyszerűen átküldi a kijelző memóriájába, amikor minden készen van. Így is működik. Természetesen bármikor lehet „átküldeni” a memóriát a kijelzőbe, de célszerű minden feliratot elkészíteni, és csak aztán másolni. A példa programban jól látható a folyamat. A szokásos println()-el lehet „nyomtatni” a feliratokat, és a display() pedig megjeleníti. Ez nekem azért szomorú hír, mert a csapadékmérőhöz elvileg felesleges lenne egy Arduino nano, és van is egy ATtiny85 modulom, ami viszont csak 8kbyte programmemóriával rendelkezik, nomeg 512 byte ram-al! Valószínűleg ezt nem fogom tudni felhasználni, mert nem fér el benne a 15kbyte-os program. Némi remény azért van, mert olvastam egy cikket, amiben megemlítették, hogy ennek a program könyvtárnak van ATTyni85-re módosított változata. Hátha az egy lebutított verzió, és elfér (meg is oldottam a problémát, a leírás vége erről szól).

Nem néztem utána, hogy pontosan milyen karakter kódtáblát valósítottak meg, de a példa programban 437-es kódlapot állított be az egyik függvény. Ebben azt hiszen nincs meg az összes magyar karakter, de nekem egyelőre ez nem kellett, így nem is foglalkoztam vele. Lehet nagyítani is, pl. 5x-ös nagyítás azt jelenti, hogy ami egy pont a karakterben, az 5×5 pont lesz. Elég darabosak a felnagyított karakterek, de használható. Így néz ki:

Kicsit macerás a tartalom kiírás pozíciójának a számítgatása. Ehhez azt kell tudni, hogy a karakter felrajzolása a bal felső sarkában kezdődik, vagyis a bal felső sarok koordinátáját kell megadni. Egy normál karakter mérete 5×8 pont (5 oszlop, 8 sor). Azonban a legtöbb karakter csak 7 sort használ. A 8. sorban csak az olyan betűk jelenítenek meg pontot, melyek lelógnak az írás vonaláról. Pl. ilyen a „g”. Ha csak számokat jelenítünk meg, és kevés a hely, nyugodtan lehet 7-el számolni. Így ha pl. a kijelző legaljára szeretnék kiírni egy számot 5x-ös nagyításban, akkor a karakter mérete 7×5 azaz 35 pixel. A kijelző magassága 64 pixel, így a kezdési pont 29. sorban lesz. Ha pl. (0,29) koordináta pontba helyezzük a kurzort a karakter kiírása előtt, a számok legeslegalsó lehetséges világító pontja a kijelző legeslegalsó sorába fog kerülni. Függőleges irányban a számok akkor is megjelennek, ha nem férnek ki, csak persze nem látszik a szöveg alja. Azonban vízszintesen nem ezt tapasztaltam. Kiírtam a hatalmas szám végére a „mm” feliratot, és ha nagyon kitoltam jobbra, akkor a második „m” betű nem jelent meg, és nem csak levágta a kijelző vége a karakter végét, mint függőlegesen.

Íme a példaprogram:

#include <Wire.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_ADDRESS 0x3C                       // A kijelző címe az I2C buszon 0x3D és 0x3C is lehet, nálam ez működött
Adafruit_SSD1306 oled_display(128, 64, &Wire, 4); // "oled_display" nevű objektum létrehozása, paraméterek: szélesség, magasság, típus (most I2C), reset pin (most konstans 4)

void setup() {

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  oled_display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);

  oled_display.clearDisplay();               //törli a képernyőt
  oled_display.setTextColor(SSD1306_WHITE);  // szöveg színe (fehér)
  oled_display.setTextSize(3);               // 5x5 pixel a karakter egy pontjának mérete
  oled_display.setCursor(0,0);               // kiírás kezdő poziciója
  oled_display.println("Uts.24h");
  oled_display.setTextSize(2);               // 5x5 pixel a karakter egy pontjának mérete
  oled_display.setCursor(104,50);            // kiírás kezdő pozíciója. A jobb alsó sarokba szeretném, így 128-24=104 oszlopban és 64-14=40 sorban kezdem.
                                             // Oszlop számításnál 128 kijelző oszlopainak száma, egy karakter szélessége 6 pont, de kétszeres mérteben, azaz 12 pont,
                                             // de mivel két betűm van összesen 24. A sorok számításánál 64 a kijelző sorainak száma, egy karakter magassága 7 pont, 
                                             // kétszeres szorzóval az 14 pont. FIGYELEM! Egy karakter valójában 8 pont, de a legalsó sorban csak egyes karakterekben
                                             // van felhasználva, pl "g" betű, ami lelóg a legalsó sorba is. az "m" nem ilyen, így nem kellett a teljes magassággal számolni.
  oled_display.println("mm");

  oled_display.setTextSize(5);               // 5x5 pixel a karakter egy pontjának mérete
  oled_display.setCursor(0,29);              // kiírás kezdő pozíciója. Balról a legelső sorban kezdem, így a sor index 0. Az oszlop index 64-(7x5)=29, ahol a 7x5 a kerekter
                                             // magassága pontokban. A számoknál nem használatos a karakterkép legalsó 8. sora, ezért lehetett itt is 7 sorral számolni.
                                             // Valamint 5x-ös nagyitás van.
  oled_display.println(6.2,1);               // Kiírtam egy számot egy tizedesjegy hosszúságban. Vigyázni kel a valós felhasználásban, mert csak három karakter fér el.
  oled_display.display();
}

void loop() {
}

…és a program futtatás eredménye:

Használat ATtiny85 alaplap esetén

Ahhoz, hogy bármilyen programot rá tudjunk tölteni egy ATtiny85 alaplapra, kell néhány kiegészítőt beszerezni a net-ről. Az z alaplap legalapvetőbb tulajdonságairól, az előkészületekről és a program feltöltésről itt találsz információt.

Az alapvető probléma az, hogy az ATtiny85 6 Kbyte felhasználható programmemóriával rendelkezik. Ez szinte semmire nem elég, de azért mégis… Miután megfejtettem, hogy az ATtiny85 alaplap kezelő könyvtárral egy csomó programot is kapok, ami a különböző hardvereket kezeli, sokkal egyszerűbbé vált a munka. Már csak az alaplapkezelőhöz kapott példaprogramokat kellett előkeresni, amik ott csücsülnek az Arduino IDE-ben a példák között. Van is példa program az OLED kezelésre is. Sajna kapásból nem használható, mert olyan nagy a mérete, hogy fordítás közben jelzi nem fog elférni. El is kezdtem kiszedegetni a felesleges részeket. Két képet is megpróbált megjeleníteni a példa program, amit a

#include „img0_128x64c1.h”
#include „digistump_128x64c1.h”

programsorokkal emelt be külső fájlokból. Ezeket a sorokat azonnal töröltem, valamint a loop()-ban azokat a sorokat, melyek ezeket a képeket a kijelzőre írják. Ami maradt, még azon is egyszerűsítettem egy kicsit, és ez maradt belőle:

#include <DigisparkOLED.h>
#include <Wire.h>

void setup() {
  oled.begin();
  oled.clear();                   // kijelző törlés
  oled.setFont(FONT8X16);         // a dupla karekterkészletet fogjuk használni
  oled.setCursor(0,0);            // kezdő pont a bal felső sarok
  oled.print(F("NAGY karakter")); // Kiírjuk a szöveget, az F() a ram megtakarítása miatt kell, mert a stringet nem másolja a ram-ba!
  delay(3000);
  oled.setFont(FONT6X8);          // szimpla karakterméretet használjuk
  oled.setCursor(0,2);            // kezdő pont a bal felső saroktól lejebb két sorral (2x8 ponttal)
  oled.print("KICSI karakter");   // Kiírjuk a szöveget

}

void loop() {
}

Meglepő, hogy a program a „Wire.h”-t használja, és nem a TinyWireM.h-et, amit az LCD kijelzőhöz adott példa programban találtam (lásd az LCD kijelzőről készült leírásom legvégén)! Úgy tudom a Wire.h nagyobb méretű, mint a TinyWireM.h, amit kifejezetten az ATtiny vezérlők miatt írtak. Esetleg itt még szintén lesz méret csökkentési lehetőség, de most még nem foglalkozok ezzel. A program futási eredménye így néz ki a kijelzőn:

Azt azért vegyük figyelembe, hogy az a kijelző 11x22mm nagyságú!

A fenti program már önmegában 4,2 Kbyte program memóriát használ, ami miatt aggasztóan kevés hely marad a további esetleges program részeknek. De legalább lehet a kijelzőre írni! A szimpla betűméret olyan kicsi a kijelzőn, hogy már szemüveggel is alig látom, így biztosan csak a dupla magas karakterszettet fogom használni. Ez ad némi reményt arra, hogy a program mérete csökkenthető. A program könyvtárban található .cpp állományokból ugyanis kiemelhető a programkód, és ha azt beillesztem a programomba, akkor lehetőségem van arra, hogy ami nem kell azt kihagyjam a a programból. Ez nem mindig célravezető, mert amit a programom nem használ, azt tapasztalatom szerint a fordító nem fogja hozzászerkeszteni a programhoz. De azért több a lehetőség a méretcsökkentésre. Találtam egy programkönyvtárat a net-en, ami még nagyobb fontokat ír a kijelzőre, most azzal is kísérletezek, hátha még nagyobb számokat tudok kiírni.

Közben eltelt néhány hét, és rengeteg időt eltöltöttem azzal, hogy a letölthető kijelző kezelő könyvtárak helyett írjak egy sajátot, ami sokkal kisebb méretű. Pénzben nem érte meg, de a siker jó érzés! Lássuk, hogyan lehet alig 2 kbyte-ba beletenni 2, 3 és 4 szeres nagyítású betűket és számokat. Ezekben a méretekben már méterekről elolvasható szöveget kapunk az alig 11x22mm-es kijelzőn. Igaz, ezekből a számokból már csak 4-5 db fér el egyszerre ezen a felületen.

A leírás elején emlegetett csapadékmérő nagyon egyszerű szerkezet, ezért szerettem volna volna OLED kijelzőt és ATtiny85 alaplapot használni. Az ATtiny85 6kbyte memóriájába esélytelen volt, hogy a készen kapott könyvtárakkal beférjen a program. Ezért pusztán virtusból elkezdtem leegyszerűsíteni a készen kapott programokat, annak érdekében, hogy mégiscsak elférjek! Viszont a programok átírása jelentős kompromisszumokkal járt:

  • Az eredeti programkönyvtárakban 2,3 vagy akár 4-szeres karakterméreteket is találtam (a fentebb leírt könyvtárban csak kétszeres méret van), de ezek mind úgy működtek, hogy tartalmazták a karakter nagyított karakterképét, tehát egy karakterhez 6, 12, 24 byte kellett. Azt találtam ki, hogy csak a 6 byte-os karakterképet fogom használni, és ezt felnagyítom programmal. Valójában csak 5 byte kell egy karakterhez, mert az első byte mindig nulla a karakterképben, ez választja el az egymás mellé rajzolt karaktereket. Bár csak a legkisebb 5×7-es (5×8) karaktermérettel fogok dolgozni, nem használok egyszeres karakter méretet, mert az annyira kicsi, hogy szemüveggel sem látom. Nálam a legkisebb nagyítás eleve a kétszeres karakterméret lett.
  • Ennyire kicsi kijelző esetén semmi szükség a teljes ASCII karakterkészletre (kb. 120 karaktert definiáltak), ami még alap méretben is 6×120=720byte helyfoglalás a programban. Pl. a fentebb használt könyvtárban van egy alap betűméret 620byte-al, és egy kétszeres 12×120=1440byte-al, a kettő együtt több mint 2 kbyte, és még egy sor programot sem írtunk. Nekem a csapadékmérőhöz elegendő összesen 17 karakter a számokkal együtt. Így mindig csak azt a karaktert kell használni, ami feltétlenül szükséges. Viszont az előző ponttal és ezzel együtt nyertünk 2kbyte-ot, azaz a rendelkezésre álló memória harmadát.
  • Mivel a kijelző mérete alapján amúgy sem számíthatunk hosszú szövegekre, nagyon primitív programot alakítottam ki, ami egyszerre csak egy karaktert rajzol fel a képernyőre. Tehát szavak esetén betűnként, számok esetén számjegyenként kell kirajzolni amit szeretnénk. A program kevésbé áttekinthető, de mivel a karakterkészlet nem teljes, egyébként is egyedi karakterkódokkal kell dolgozni, tehát a szöveg a kódban nem felismerhető. Kommentezni kell, ha ez zavaró!
  • A programon úgy is rengeteget lehet egyszerűsíteni, ha korlátozzuk a karakterek megjelenési helyét. Az előzőekben megismert könyvtárakban a karakterek gyakorlatilag bárhol megjelenhettek, bitenként lehetett megmondani a kezdőpontot sorok és oszlopok szerint is. Én ezen annyit egyszerűsítettem, hogy a karakterek csak a 8db memória lap valamelyikén kezdődhetnek (ezeket mostantól soroknak fogom hívni). Egy memória lap (sor) 128 byte-ból áll, mert a kijelző 128×64 bit méretű. Erről mindjárt bővebben is olvashatsz. Ezzel a módszerrel azonban a karakterek nem jelenhetnek meg összevissza, csak egy sorban lehetnek, bár ezeket a sorokat lehet egymáshoz képes 8 pontonként fel és le tologatni. Gyakorlatilag csináltam egy karakteres LCD kijelzőhöz hasonlót. Annyival tud többet, hogy a karakterek mérete is állítható korlátozottan.
  • Mondanom sem kell, hogy az egyéb grafikus kijelzésekről, pontok, vonalak, bitképek, teljesen lemondtam.

Ennyi kompromisszum után már úgy sejtettem, hogy el fogok férni a memóriában. Viszont a programok megírásához sokkal alaposabban meg kellett ismerni a kijelzőt. Vettem hát az adatlapot, és áttanulmányoztam. Amit megértettem, azt most le is írom, hogy másnak már ne kelljen ezzel küzdeni.

Fontos tudni, hogy a kijelzőt a SSD1306 karaktervezérlő chip vezérli, tehát ennek a parancskészletét kell megismerni. Azon nem szabad meglepődni, hogy ebben a chip-ben van 1kbyte memória, ennek a bitjeit kell a megfelelő byte-okban 1-re vagy 0-ra állítani, és a kijelző ezeket a biteket fogja a megfelelő kijelző szegmensben be vagy kikapcsolni. A kijelző a következő memória térkép szerint jeleníti meg a biteket:

A képeket https://www.electronicwings.com/sensors-modules/ssd1306-oled-display weboldalról szedtem le, de szerintem ezek szerepelnek a chip adatlapján is, így remélhetőleg nem sértek szerzői jogokat!

A két fenti képen látható, hogy nagyon egyszerű algoritmusról van szó, sorban végig olvassa az 1kbyte memóriát, és kirakja a képernyőre soronként (byte azaz 8bit oszlopokban). Nekünk tehát az a feladatunk, egy egy karakter megjelenítésekor megfelelő sorrendben küldjük a byte-okat. Ha egyszeres karakterméretet szeretnénk megjeleníteni, és elfogadjuk, hogy a karakter egy memória lapon (PAGE) jelenik meg, akkor csak kiküldjük egymás után a karakter byte-jait, és megjelent a karakter. Ezt az írási módot hívja a chip leírása lap (PAGE) címzési módnak. Ábrával talán könnyebben érthető:

Szerencsére nem kell egy teljes sort végig írnunk, egy adott sor bármely oszlopa magadható kezdőcímnek, így az írást kezdhetjük bárhol a soron belül.

Ezt a címzési módot egy kicsit még tovább is fejlesztették, amit horizontális címzési módnak hívnak:

Mint az ábrából látható, a chip intelligens annyira ebben az írási módban, hogy amennyiben elértük egy soron belül (memória lapon belül) az utolsó oszlopot, akkor magától növeli a sor címét és a következő sor elejéről folytatja.

De azért nem adja ennyire egyszerűen magát a kijelző, ennél többet tud. A memóriát ugyanis vertikálisa, azaz függőlegesen is lehet írni:

Ehhez sem kell sok magyarázat, a következő byte-ot a következő sor ugyanazon oszlopába írja, és automatikusan oszlopot vált, ha elértük az utolsó sort.

Sőt! A horizontális és a vertikális címzési módnak van egy roppant hasznos további tulajdonsága! Meg lehet adni egy sorral és oszloppal kijelölt kezdőcímet, valamint egy sorral és oszloppal kijelölt végcímet is. Ekkor csak az így kijelölt területet fogja teleírni soronként (laponként), vagy oszloponként.

A függőleges címzési mód számomra nagyon hasznos volt, mert kijelöltem a megjelenítendő karakter területét, és csak sorban kiküldtem a karakterkép byte-jait. Semmivel nem kellett foglalkozni.

SSD1306 elemi parancsainak működése:

Elsőként nézzük meg, hogyan is kell parancsot küldeni a chip-nek. Ez nagyon egyszerű, az I2C buszon keresztül küldünk neki egy 0x00h byte-ot, és a chip tudja, hogy ezt parancsok követik. Tetszőleges számú parancsot küldhetünk egyszerre, nem kell az I2C buszon lezárni egy parancs után a kommunikációt. Tetszőlegesen küldhetünk parancsokat és adatokat keverve is, mert az adatok küldését a 0x40h byte-nak kell megelőzni, így a parancsok és az adatok elkülönülnek a chip számára. A parancsok több byte-osak is lehetnek, ekkor az első byte mondja meg, hogy mit állítunk be, és a chip pontosan definiált mennyiségű további byte-ot vár. Egyes parancsok magukban tartalmazzák az adatot is, vagyis az egy byte első négy bitje a parancs, a második négy bit pedig a hozzá tartozó adat. Kicsit zavaros így, de a példákból minden ki fog derülni!

Az SSD1306 chip parancsai

Címzési mód beállítása (0x20h)

Ez egy két byte-os parancs. Az első bájt (0x20h) az oszlopcím beállítására vonatkozó parancsot adja meg, a következő byte pedig a címzési módot. Ha valahol a parancsok sorozatában találkozik a chip egy 0x20h paranccsal, akkor vár még egy byte-ot, melynek jelentése:

0x00h – Oldalcímzési mód (PAGE mode)

0x01h – Vízszintes címzési mód

0x02h – Függőleges címzési mód

Én a leírásomban általában sorokról beszéltem, de a következő részben megtartottam a leírásban található „oldal” kifejezést. A memória „lap” vagy „oldal” ugyanis jobban illeszkedik a témához, a „sor” kifejezés, már a kijelzett szövegekre jellemző, de itt most memória területekről beszélünk. Bár a kettő végül is ugyanaz!

Oldal címzés tulajdonságai:

Emlékeztetőül az oldal címzés működésének ábrája:

  • Oldal címzési módban a kijelző RAM írása után az oszlopcím-mutató automatikusan 1-el nő.
  • Ha az oszlopcím-mutató eléri az oszlop végcímét, az oszlopcím-mutató visszaáll az oszlop kezdőcímére, de az oldalcím-mutató nem változik, ugyanazt a lapot írhatjuk továbbra is.
  • Oldal címzési módban meg kell adni a memória lap számát PAGE számot), és azt, hogy az oldalon belül hányadik oszloptól kezdjük az írást. Ennek részletei később.

Vízszintes címzési mód tulajdonságai:

Emlékeztetőül az vízszintes címzés működésének ábrája:

  • Vízszintes címzési módban a kijelző RAM írása után az oszlopcím-mutató automatikusan növekszik 1-el.
  • Ha az oszlopcím-mutató eléri az oszlop végcímét, az oszlopcím-mutató visszaáll az oszlop kezdőcímére, és az oldalcím-mutató 1-el növekszik.
  • Amikor mind az oszlop, mind az oldalcím mutatói elérik a végcímet, a mutatók visszaállnak az oszlop kezdőcímére és az oldal kezdőcímére (ez nem feltétlenül a 0,0 pont, mit ahogyan az ábrán látjuk).
  • Valamikor az adatok fogadása előtt ki kell jelölnünk az írandó terület bal felső, és jobb felső sarkát, tehát azt, hogy melyik oldal melyik oszlopába írja a chip az első byte-ot, valamint azt, hogy melyik lap melyik oszlopába kerüljön az utolsó byte. Ennek beállításáról később!

Függőleges címzési mód:

Emlékeztetőül az függőleges címzés működésének ábrája:

  • Függőleges címzési módban a kijelző RAM írása után az oldalcím-mutató automatikusan növekszik 1-el.
  • Ha az oldalcím-mutató eléri az oldal végcímét, akkor az oldalcím-mutató visszaáll az oldal kezdőcímére, és az oszlopcím -mutató 1-el nő.
  • Amikor mind az oszlop-, mind az oldalcím mutatói elérik a végcímet, a mutatók visszaállnak az oszlop kezdőcímére és az oldal kezdőcímére.
  • Valamikor az adatok fogadása előtt ki kell jelölnünk az írandó terület bal felső, és jobb felső sarkát, tehát azt, hogy melyik lap melyik oszlopába írja a chip az első byte-ot, valamint azt, hogy melyik lap melyik oszlopába kerüljön az utolsó byte.

Oldalcím megadása oldal címzési módban (0xB0h – x0B7h)

Annak érdekében, hogy minél kevesebb kommunikáció történjen az I2C buszon, ez a beállítás önálló, önmagában adatot is tartalmazó parancs. A memória oldal számát (PAGE számot) lehet beállítani vele. Oldal beállítása 0xB0h-tól a 0xB7h-ig lehetséges, ahol az első négy bit hexa B, és a következő négy bitben lehet megadni az oldal címét.

Oszlopcím beállítása oldal címzési módban (0x00h – 0x0Fh valamint 0x10h -0x1Fh)

Ezek a parancsok is önálló, önmagukban adatot is tartalmazó parancsok. Ezért kellett két külön parancs, mert 128 oszlopból kell kiválasztani azt amire írni akarunk. Kicsit meglepő, hogy két lépés kell a beállításhoz (alsó és felső 4 bit kiküldése), de ez biztosan optimálisabb valamilyen szempontból, mint egy konkrét parancs azt követő byte-al, amiben az oszlopcímet megadjuk. Így a konkrét oszlop címet először alsó és felső négy bitre kell szétválasztani, majd a parancs felső négy bitje mögé illeszteni. A példa programban csak a képernyő törlésnél használtam, ott meg a 0 pontot kellett beállítani, tehát 0x00h és 0x10h byte-okat kellett kiküldeni számolgatás nélkül. Ha majd megnézzük a további parancsokat, látható lesz, hogy semmilyen más parancs nem kezdődik hexa 0, és 1-el. Tehát ezeket a tartományokat le is foglalta a chip. Viszont így bármikor külön bevezető parancs nélkül lehet oldalt váltani, és oldalon belül oszlopot beállítani. Nézzünk egy példát! Tegyük föl, hogy szeretnénk a memória írását a 3. sorban kezdeni, azon belül pedig a 5. oszlopban. Ekkor az I2C buszon a következő byte sorozatot kell kiküldeni: 0x00h, 0xB02, 0x04. És ezzel kész is vagyunk.

Oszlopcím beállítása vertikális és horizontális címzési mód esetén (0x21 H):

  • Ez egy három byte-os parancs. Az első byte az oszlopcím (0x21 H) beállítására vonatkozó parancsot adja meg.
  • A második byte az oszlop kezdőcímét, a harmadik bájt pedig az oszlop végcímét adja meg.
  • Ez a parancs az oszlopcím mutatót az oszlop kezdőcímére állítja.

 Oldalcím beállítása vertikális és horizontális címzési mód esetén (0x22 H):

  • Ez is egy három byte-os parancs. Az első byte megadja az oldalcím beállítására vonatkozó parancsot (0x22 H).
  • A második byte az oldal kezdőcímét, a harmadik bájt pedig az oldal végcímét adja meg.
  • Ez a parancs az oldalcím mutatót az oldal kezdőcímére is állítja.

Példa az oszlop- és sorcím -mutató mozgására:

A következő példában a függőleges címzési módot használjuk. Épp egy négyszeres karaktert akarunk kirajzolni aminek magassága 32 bit, a szélessége pedig 20 bit (5×8 pontmátrix lett felnagyítva). A lap kezdőcíme a példában legyen 0 (a karaktert a kijelző legtetejére rajzoljuk fel), akkor a lap végcíme 3. A kezdő oszlopcím legyen 0 (tehát a karaktert a bal felső sarokba rajzoljuk), ekkor az oszlop végcíme 19. A következő byte sorozatot kell kiküldeni a chip-nek, mielőtt az adatokat küldenénk:

0x00h  Ezzel jelezzük, parancsokat fogunk küldeni

0x20h  Írási (címzési) mód beállítása következik

0x01h  Függőleges írási mód

0x21h  Első és utolsó oszlop megadása

0x00h  Kezdő oszlop cím (0)

0x13h  Záró oszlop cím (decimálisan ez 19)

0x22h  Első és utolsó lap cím

0x00h  Az a lap, ahol a karakter kezdődik (0)

0x03h  Az a lap, ahol a karakter befejeződik (3)

A 0x21h és a 0x22h parancsok kiadásának sorrendje mindegy. Sőt, ha beállítottuk pl. a kezdő és záró lapcímet, több kezdő és záró sorcím megadást is végezhetünk a későbbiekben.

Kijelző óraosztási arány/ oszcillátor frekvencia beállítása (0xD5h):

Ezt a parancsot nem használtam, nem is értem pontosan, hogy mikor lényeges a beállítása. A kijelző inicializálásakor a 0xD5h értéket küldtem ki. További részleteket az adatlapból lehet megtudni.

Multiplex arány beállítása (0xA8h):

Ez a parancs két byte-os. A 0xA8h jelzi, hogy a multiples arány küldése következik, és a következő byte-on várja az adatot. Egy 128×64-es méretű kijelzőn ennek értéke 0x3Fh, ami 63 decimálisan. Próbálgattam az értéket megváltoztatni, és azt tapasztaltam, hogy a kijelző legfelső sorába írt tartalom egyre lejjebb jelent meg a kijelzőben, a felső sorok teljesen sötétek maradtak. A kiírt tartalom fényereje pedig megnőtt. Tehát ez a paraméter azzal van összefüggésben, hogy a chip több részletben jeleníti meg a kijelző tartalmát.

A kijelző kezdővonalának beállítása (0x40h – 0x7Fh):

Ezt a parancsot sem használtam, az alapértelmezett 0x40h értéket állítottam be (amit egy kész programból lestem el). Nem pontosan értem mire való, további részletek az adatlapban. 

Kontrasztvezérlés beállítása (0x81h):

Ez a parancs állítja be a kijelző fényerejét. A chip 256 fényerő lépéssel rendelkezik 0x00h -tól 0xFFh -ig. A szegmens kimeneti árama nő a kontrasztlépés értékének növekedésével az adatlap szerint. Én kitöltési tényezőre gyanakszom, de lehet, hogy így is van!

Az előtöltési időszak beállítása (0xD9h):

Beidézem az adatlap szövegét, bár nem értem: Ezzel a paranccsal állítható be az előtöltési időszak időtartama. Az intervallumot a DCLK számában számolják, ahol a RESET 2 DCLK -val egyenlő.

A VCOMH kijelölés megszüntetésének beállítása (0xDBh):

Ez a parancs a VCOMH szabályozó kimenetét állítja be. Hogy ez micsoda, abban sem tudok segíteni!

Teljes kijelző bekapcsolva (0xA4h/0xA5h):

  • Az 0xA4h parancs újra indítja a kijelzőt.
  • Az 0xA5h parancs arra kényszeríti a kijelzőt, hogy „BE” legyen, függetlenül a kijelző RAM -tartalmától.

Normál/fordított kijelző beállítása (0xA6h/0xA7h):

  • Az A6 h parancs normál megjelenítésre szolgál.
  • Az A7 h parancs inverz megjelenítésre szolgál.   

A kijelző BE/KI beállítása (0xAEh/0xAFh):

  • 0xAEh: A kijelző kikapcsolása. Memóriában lévő információ nem vész el, csupán az összes világító pixelt lekapcsolja az SSD1306 vezérlőchip. Ebben az állapotban is fogad parancsokat és adatokat a chip.
  • 0xAFh: A kijelző bekapcsolása. Ami a memóriában van, az meg is jelenik.

Példa program működése és tulajdonságai

Miután megismertük a kijelző parancsait, következzék a példa program, amivel karaktereket lehet megjeleníteni a kijelző majdnem tetszőleges helyén. Ne felejtsük el közben, hogy a legfontosabb cél a program minimalizálása volt. kötöttünk jelentős kompromisszumokat, alig okosabb immár a kijelzőnk, mint egy karakteres LCD kijelző. Nagyából egy 4 soros 11 karakteres kijelzőnek felel meg. Azért nem lehet megmondani pontosan, mert kis trükközéssel az úgynevezett proporcionális megjelenítésre is alkalmas. Azokat a karaktereket, melyek csak kevés helyet foglalnak vízszintesen pl. „l” vagy „i”, kisebb helyen lehet megjeleníteni. A trükk annyi, hogy ezeket kell legelőször kirajzoltatni a helyükre, és csak utána a többi karaktert jól összezsúfolva. A használat igen kényelmetlen, mert betűnkén kell meghívni a betű rajzoló függvényt, és a pontos pozíciót előtte ki is kell számolgatni. Azért ez ennyire nem macerás, egyszerű esetben pillanatok alatt meg van az egész! A karakterek, csak a lapok felső sorában kezdődhetnek (tehát a 0, 1, 6 stb. sorokban illetve eddig lapoknak hívtam), de tetszőleges oszlopban. A karakterek minimum két lapot foglalnak el, de beállítható 3 és 4 szeres nagyítás is. Ekkor a legutolsó lap értelemszerűen már csak 4. lehet pl. négyszeres nagyításnál, különben a betű „kilóg” a kijelzőről. Az oszlopok kiszámolása sem túl macerás. A legkisebb kétszeres nagyításnál 11 pontonként követhetik egymást a betűk, pl. négyszeres nagyitásnál 21 pontonként, de inkább 23 pontonként, ha azt akarjuk, hogy legyen közöttük némi rés, és jól olvasható legyen. Íme a példa programmal varázsolt kijelzési minta:

Itt a kisebb betűk 3x-os nagyítással készültek, a nagyobb számok 4x, a „mm” felirat pedig kétszeressel. Az egész fejlesztést a csapadékmérő műszeremhez csináltam, és ott csak ezeket az infókat kellett kiírni (ez éppen azt jelentené, hogy az utolsó egy napban 123,4mm csapadék esett).

A program elve roppant egyszerű. Definiáltam karaktereket a lehető legkisebb, „legdurvább” felbontással 5×7 pontból. Na persze találtam kész adatokat a letölthető könyvtárakban, csak a 0. sort kellett kitörölni, ami minden karakternél 0x00h volt, tök feleslegesen. Ezzel máris spóroltam 17 byte-ot, gondoltam egész jól indul a dolog!

Második lépésben megírtam a setup() részt, pontosabban a kijelző inicializálását. Ezt bevallom ellestem az egyik programkönyvtárból. Ott külön függvényt írtak erre a feladatra, ami byte-onként küldte ki egy tömbből az inicializáláshoz szükséges parancsokat. Néhány byte-ot lefaragtam a programból azzal, hogy nem küldtem ki minden egyes parancs előtt az I2C átvitel indítását és a végén a lezárását. Kihasználtam, hogy a kijelző egy menetben sok parancsot képes fogadni. Egyetlen korlát az I2C 32 byte-os adatmennyiség korlátja lett volna, de a parancsok kevesebb adatot jelentettek. Viszont az I2C Wire.beginTransmission() és a Wire.endTransmission()  függvények közé rakott kb. 20 byte ATtiny85 esetén nem ment át probléma mentesen. A programot Arduino UNO-val fejlesztettem, mert azzal kényelmesebb volt a munka. Ott semmi gond nem volt ezzel. Amikor azonban áttértem az ATtiny85 alaplapra, meglepetés ért! A kijelző teljesen sötét maradt. Hosszú próbálkozás következet, a beépített LED-el debug-oltam a programot. Több nap (vagy inkább óra) szívás után jöttem rá, hogy a kijelző beállító parancsokat nem lehet egyben kiküldeni, valahol közben le kellett zárni az I2C küldést és újra megnyitni. Így már minden rendben ment. Szerintem az ATtiny85 alaplap kezelőhöz adott, butított Wire() függvényekkel lehet valahol a hiba, de nem kerestem meg, elég annyi, hogy működésre tudtam bírni!

Harmadik lépés volt a kijelző törlés megírása. Ezt még mindig a setup() részben követtem el. Ehhez a lap címzési módot volt célszerű használni, és három egymásba ágyazott for() ciklussal le lehetett rendezni a törlést. Itt is beleakadtam az I2C adatküldési korlátba. Át is kellett írni, nem küldhettem ki 4x32byte-ot, mert nem törölt rendesen (csak az Arduino Uno-n). 8x16byte-al viszont már rendben kimentek a 0x00h byte-ok a kijelzőre!

Negyedik lépés volt a karakter_write() függvény megalkotása. Ennek bemenő paramétere a megjelenítendő karakter kódja (saját kód, nem ASCII), a nagyítás mértéke ami 2, 3 és 4 lehet, valamint a sor és oszlop paraméterek. A sor mint azt fentebb említettem 0 – 6-ig adható meg. De ha éppen kettőnél nagyobb nagyítást alkalmazunk, akkor 4 vagy 5 lehet a maximális érték. Az oszlop szabadon használható 0 és 128-karakterhossz között. Karakterhossz = 5 x nagyítás.

A karakter rajzoló függvény nagyon buta. Csak annyit csinál, hogy előveszi a karakter képét a setup()-ban létrehozott tömbből, és az 5 byte-ot kirajzolja a képernyőre, azaz kiküldi oszloponként az adatokat. Bonyolítja a dolgot, hogy nagyítani kell, ezért egy oszlop adatait egy long változóba léptetem bele! Ahol a karakterképben egy 1-es bitet találok, oda a nagyítástól függően 2, 3, vagy 4 db 1-es bitet léptetek be. Egy oszlop adatait a nagyítás száma alapján meg is kell ismételni, majd jöhet a következő byte a karakterképből, és így alakul ki a felnagyított karakterkép. Mielőtt a karakterkép byte-jait kiléptetem, a kijelzőt függőleges címzésre állítom be, és megadom a karakter felső és alsó sorát, valamint a jobb és bal oldali szélső oszlopokat. Ekkor már csak sorban küldeni kell a byte-okat, a többit a kijelző elintézi. Több egymásba ágyazott for, és néhány bitművelet az egész. Nem volt benne nagy gyakorlatom, így napokig szenvedtem vele, de a kitartás meghozta gyümölcsét. A program mérete 2116 byte. A karakterképek a memóriában vannak és elhasználtunk 116 byte ram-ot is. Ha esetleg kell a ram, akkor még a karakterképek berakható a programmemóriába. És itt a munka gyümölcse:

#include <Wire.h>
#include <EEPROM.h>                // Ehhez előbb át kell másolni az Arduino alap könvtár EEPROM.h állományát 
                                   //a digistump alaplap könyvtárba, vagy bemásolni a forrás program ino állománya mellé

// Karakter bit térkép definiciók. Nekem csak az alábbi néhány karakter kellett.
// Ha további karakterekre lenne szükség, a digispark alaplap kezelő Digispark alkönyvtárában
// kb. itt: C:\Users\Zoli\AppData\Local\Arduino15\packages\digistump\hardware\avr\1.6.7\libraries\DigisparkOLED
// A font6x8.h állományból lehet kimásolgatni karakterenként. Az első byte nem kell, mert az mindig 0.
const byte font_5x8[] = {
 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0     0-as karakterkód
 0x00, 0x42, 0x7F, 0x40, 0x00, // 1     1-es karakterkód
 0x42, 0x61, 0x51, 0x49, 0x46, // 2     2-es karakterkód
 0x21, 0x41, 0x45, 0x4B, 0x31, // 3     3-as karakterkód
 0x18, 0x14, 0x12, 0x7F, 0x10, // 4     4-es karakterkód
 0x27, 0x45, 0x45, 0x45, 0x39, // 5     5-ös karakterkód
 0x3C, 0x4A, 0x49, 0x49, 0x30, // 6     6-os karakterkód
 0x01, 0x71, 0x09, 0x05, 0x03, // 7     7-es karakterkód
 0x36, 0x49, 0x49, 0x49, 0x36, // 8     8-as karakterkód
 0x06, 0x49, 0x49, 0x29, 0x1E, // 9     9-es karakterkód
 0x00, 0x60, 0x60, 0x00, 0x00, // .     10-es karakterkód
 0x08, 0x08, 0x08, 0x08, 0x08, // -     11-es karakterkód
 0x00, 0x00, 0x00, 0x00, 0x00, // sp    12-es karakterkód
 0x7F, 0x04, 0x08, 0x10, 0x7F, // N     13-as karakterkód
 0x7C, 0x12, 0x11, 0x12, 0x7C, // A     14-es karakterkód
 0x7F, 0x09, 0x09, 0x09, 0x06, // P     15-ös karakterkód
 0x7C, 0x04, 0x18, 0x04, 0x78, // m     16-os karakterkód
 // Példa karakter (0): 
 // .xxx.  00111110
 // x...x  01010001
 // x..xx  01001001
 // x.x.x  01000101
 // xx..x  01111110
 // x...x
 // .xxx.
 // .....
};

void setup()
{
  // OLED SSD1306 meghajtó chip inicializálása
	Wire.begin();
  Wire.beginTransmission(0x3C); // Az SSD136 chip kijelölése az I2C buszon
  Wire.write(0x00);             // ezzel jelezzük az SSD1306 chip-nek, hogy parancsot fogunk küldeni
  Wire.write(0xAE);             // A kijelzőt sleep módba kapcsoljuk (lekapcsolja a pixeleket, parancsokat, adatokat fogad)
  Wire.write(0x20);             // Memória címzési mód beállítása következik
  Wire.write(0x00);             // Horizontális címzési mód bekapcsolása (00=Horizontal, 01=Vertical, 10=Page (RESET) mód)
  Wire.write(0xB0);             // Horizontális módban a start page címe (0-7 lehet, mert 8 page-re van osztva a memória)
  Wire.write(0x00);             // Oszlop cím alsó értéke
  Wire.write(0x10);             // Oszlop cím magas értéke
  Wire.write(0x40);             // --set start line address
  Wire.write(0xC8);             // Set COM Output Scan Direction
  Wire.write(0x81);             // A fényerő beállítása következik 0x00 - 0xFF
  Wire.write(0xFF);             // A fényerőt a maximumra állítjuk
  Wire.write(0xA1);             // Set Segment Re-map. A0=address mapped; A1=address 127 mapped. 
  Wire.write(0xA6);             // Set display mode. A6=Normal; A7=Inverse
  Wire.write(0xA8);             // Set multiplex ratio(1 to 64) 63
  Wire.write(0x3F);
  Wire.write(0xA4);             // Output RAM to Display, 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
  Wire.endTransmission();       //I2C küldés vége

  // Az inicializáló parancsok Arduino UNO esetében egyben kiküldhetők voltak (nincs összesen 32 byte), azonban ATtiny85 esetében
  // egyszer le kellett zárni a kommunikációt, és újra nyitni, mert különben a kijelző nem kapcsolt be. Érdekes, mert adatból 
  // lehet egyszerre 32 byte-ot küldeni, lásd képernyő törlés. Az I2C buszon egy menetben csak 32 byte-ot lehet küldeni, de ebben az esetben
  // ez nem volt igaz.
  Wire.beginTransmission(0x3C); //az SSD136 chip kijelölése az I2C buszon
  Wire.write(0x00);             // ezzel jelezzük az SSD1306 chip-nek, hogy parancsot fogunk küldeni
  Wire.write(0xD3);             // Set display offset. 
  Wire.write(0x00);             //00 = no offset
  Wire.write(0xD5);             // --set display clock divide ratio/oscillator frequency
  Wire.write(0xF0);             // --set divide ratio
  Wire.write(0xD9);             // Set pre-charge period
  Wire.write(0x22);
  Wire.write(0xDA);             // Set com pins hardware configuration 
  Wire.write(0x12);  
  Wire.write(0xDB);             // --set vcomh
  Wire.write(0x20);             // 0x20,0.77xVcc
  Wire.write(0x8D);             // Set DC-DC enable
  Wire.write(0x14);
  Wire.write(0xAF);             // kijelző bekapcsolása (pixelek működni kezdenek)
  Wire.endTransmission();//I2C küldés vége

	// Képernyő törlés, végig írjuk a kijelző memóriát 0-val
  // A memória 8 lapra van osztva, ezt kell beállítani először
  // egy lapon belül 128 byte-ot kell írni, de az I2C egyszerre csak 32 byte-ot tud kiírni.
	for (byte s=0;s<8;s++)
  {
    Wire.beginTransmission(0x3C); //az SSD136 chip kijelölése az I2C buszon
    Wire.write(0x00);              // Azt jelezzük ezzel az SSD1306-nak, hogy parancsot fogunk küldeni
    Wire.write(0xB0 + s);          // Kijelöljük a memória lapot (0xB0 - 0xB7)
    Wire.write(0x00);              // Oszlop cím alsó része
    Wire.write(0x10);              // Oszlop cím felső része (azért van két részre bontva, mert a parancsok cím tartományokra vannak osztva
                                   // és az első 4 bit fenn van tartva a parancs értelmezésére, ezért két részletben lehet kiküldeni a 8 bites címet,
    Wire.endTransmission();
		for (byte i=0;i<8;i++) { 
      Wire.beginTransmission(0x3C); //az SSD136 chip kijelölése az I2C buszon
      Wire.write(0x40); // Azt jelezzük ezzel az SSD1306-nak, hogy adatokat fogunk küldeni
			for (byte o=0;o<16;o++) {	Wire.write(0);}
      Wire.endTransmission();
		}
	}

  //"mm" felirat a csapadékmennyiséggel azonos alap magasság ban kisebb betűmérettel
  karakter_write(16,2,6,100);     //m
  karakter_write(16,2,6,112);     //m
  //1 NAP felirat
  karakter_write(12,3,0,0);       //sp  ez az előző kijelzés utolsó fázisának egyesét törli ("10 nap" egyese)
  karakter_write(1,3,0,15);       //1
  karakter_write(13,3,0,43);      //N
  karakter_write(14,3,0,61);      //A
  karakter_write(15,3,0,79);      //P

  karakter_write(10,3,5,64);      //előre kitesszük a tizedes pontot, mert annyira összezsúfoljuk a számokat, hogy jobbról 
                                  //is és balról is levágná a számokat, ha ezt tennénk ki utoljára. A méretét is kisebbre
                                  //veszük, mert a 4-szeres méret nagyon nagy. 
  karakter_write(1,4,4,0);        // számjegy kiírása
  karakter_write(2,4,4,21);       //számjegy kiírása
  karakter_write(3,4,4,43);       //számjegy kiírása
  karakter_write(4,4,4,76);       //egyes helyiérték kiírása (ez már tizedes értéknek látszik, mert előtte van egy pont)
}

void loop()
{
  delay(2000);
  // kijelző kikapcsolása
  Wire.beginTransmission(0x3C); //az SSD136 chip kijelölése az I2C buszon
  Wire.write(0x00);             // Azt jelezzük ezzel az SSD1306-nak, hogy parancsot fogunk küldeni
  Wire.write(0xAE);             // kijelző kikapcsolása (csak a pixelek nem világítanak, parancsokat, adatokat fogad)
  Wire.endTransmission();       //I2C küldés vége

  delay(2000);
  //bekapcsoljuk a kijelzőt
  Wire.beginTransmission(0x3C); //az SSD136 chip kijelölése az I2C buszon
  Wire.write(0x00); // Azt jelezzük ezzel az SSD1306-nak, hogy parancsot fogunk küldeni
  Wire.write(0xAF);      // kijelző bekapcsolása (pixelek működni kezdenek)
  Wire.endTransmission(); //I2C küldés vége
}

/***************************************************************************************************
 * Ez a függvény felrajzol egy karaktert a képernyőre. Mivel nincs teljes ASCII kódtábla egyéni    *
 * karakterkódokat használok. A függvény egyszerre csak egy karaktert rajzol fel, tehát szöveg     *
 * esetén betűnkén kell meghívni.                                                                 *
 * Paraméterek:                                                                                    *
 *   - kod, ez a karakter egyedi általam önkényesen kialakított kódja, a font_5x8[] tömb határozza *
 *          meg azzal, hogy milyen sorrendben töltöm fel az 5db karakterképet tartalmazó byte-al.  *
 *   - nagyitas, ez a paraméter határozza meg a felrajzolt karakter méretét, lehet 2,3,4 szeres.   *
 *          A maximális nagyitás 4, mert long változót használok, és abban csak négy byte fér el   *
 *          (a long mérete 32 bit azaz 4 byte)                                                     *
 *   - kezdo_lap, ez a paraméter határozza meg, hogy a karakter bal felső sarka melyik memória     *
 *          lapon fog kezdődni. A 128x64-es kijelző 8 lapra van bontva, így értéke 0-7-ig. Mivel   *
 *          a legkisebb karakter a 2-es nagyítás, legfeljebb 6 lehet a maximális mérete, de pl.    *
 *          4-es nagyításnál már csak a 4. lapon kezdhetjük a karakter megjelenítését.             *
 *   - kezdo_oszlop, ez a paraméter mondja meg, hogy hol kezdődik a karakter bal oldali legelső    *
 *          oszlopa. Egy karakter bárhová helyezhető egy soron belül, így ennek értéke 0-127-ig    *
 *          bármi lehet, de pl. egy 2 szeres nagyítású betű legfeljebb a 117-ban kezdhető,         *
 *          mert a karakter mérete 10 oszlop.                                                      *
 *                                                                                                 *
 * Működése:                                                                                       *
 *   Először a kijelzőt átkapcsolja vertikális módba, azaz a fogadott adatokat a kijelző          *
 *   föntről lefelé jeleníti meg, majd amikor elérte a kijelölt rajzolási terület alját,            *
 *   arrébb meg egy oszloppal, és ismét föntről lefelé jeleníti meg a fogadott byte-okat.          *
 *   A megadott lap, és oszlop alapján kiszámolja a karakter bel felső pontját, és a jobb         *
 *   alsó pontját, és ezt megadja a kijelzőnek.                                                    *
 *   A nagyítás paraméter alapján veszi a karakter egy byte-ját, és belépteti egy long változóba   *
 *   úgy, hogy annyi bitet léptet be egy karakter bit alapján, amennyi a nagyítás. Pl. négyes      *
 *   nagyítás esetén 4 azonos bitet léptet egy db bit helyett. Kétszeres nagyításnál csak kettőt   *
 *   stb. Ha elkészült a long változó, akkor kiküld annyi bájtot a kijelzőre, amennyi a nagyítás   *
 *   mértéke, tehát pl. 3-szoros nagyításnál 3 byte-ot. Aztán veszi a karakterkép következő        *
 *   byte-ját, azt is felnagyítja egy long változóba és kiküldi. Ezt a műveletsort 5-ször végzi    *
 *   el, mert egy karakterkép 5 byte-ból áll                                                       *
 ***************************************************************************************************/
void karakter_write(byte kod, byte nagyitas,byte kezdo_lap,byte kezdo_oszlop)
{
	byte karakterbyte;
	unsigned long nagy_karakter=0;
	unsigned long nagy_karakter2=0;
	bool k;
	unsigned long leptetbyte;  //ebbe fogjuk a 32 bites karakterképet byte-onként kiléptetni
	unsigned long maskbyte=255; //ezzel maszkoljuk ki a 32 bites karakteroszlopból az egy byte-ot
	byte maskbit=128;
	Wire.beginTransmission(0x3C);              //az SSD136 chip kijelölése az I2C buszon
	Wire.write(0x00);                          //Ezzel jelezzük, parancsokat fogunk küldeni
	Wire.write(0x20);                          //Írási mód beállítása következik
	Wire.write(0x01);                          //Függőleges írási mód
	Wire.write(0x21);                          //első és utolsó oszlop megadása
	Wire.write(kezdo_oszlop);                  //Kezdő oszlop cím
	Wire.write(kezdo_oszlop+(nagyitas+1)*5);   //záró oszlop cím
	Wire.write(0x22);                          //első és utolsó lap cím
	Wire.write(kezdo_lap);                     //Az a lap, ahol a karakter kezdődik
	Wire.write(kezdo_lap+nagyitas-1);          //az a lap, ahol a karakter befelyeződik
	Wire.endTransmission();                    //kommunikáció vége
	for (byte i=0;i<5;i++)
	{
    //Serial.println(i);
		maskbit=128;                             //maszkbit 1 bitjét fogjuk léptetni jobbra
		karakterbyte=font_5x8[kod*5+i];          //ez a karakter soron következő karakterbyte-ja
		nagy_karakter=0;                         //ebbe léptetjük be a biteket többszörözve
		for (byte j=0;j<8;j++)
		{
			k=karakterbyte & maskbit;                //kimaszkoljuk a karakterbyte bitjét
			nagy_karakter=nagy_karakter << nagyitas; //léptetjük a 32 bites karaktert 
			if (k>0)                                 //ha egy volt az adott bit, akkor 1-esekkel töltjük fel a léptetéssel felszabadult helyet
			{
				switch (nagyitas)
				{
					case 2:
						nagy_karakter |= 3;               //"11"
						break;
					case 3:
						nagy_karakter |= 7;               //"111"
						break;
					case 4:
						nagy_karakter |= 15;             //"1111"
						break;
				}
			}
			maskbit=maskbit >> 1;                    //léptetjük a maszkoló bitet, hogy kimaszkolhassuk a következő bitet a karakterképből
		}
		//itt kell a kijelzőre küldeni a 32 bitet
    Wire.beginTransmission(0x3C);              //az SSD136 chip kijelölése az I2C buszon
    Wire.write(0x40);                          //Ezzel jelezzük, hogy adatokat fogunk küldeni
		for (byte m=0;m<nagyitas;m++)              //a nagyitás miatt többször ugyanazt írjuk ki de mindig eggyel jobbra a következő oszlopba
		{
			nagy_karakter2=nagy_karakter;            //többször fog kelleni az oszlop tartalom
			for (byte l=0;l<nagyitas;l++)   
			{
				leptetbyte=nagy_karakter2 & maskbyte;  //ebben van az adott oszlop aktuális byte-ja
				Wire.write(leptetbyte);                //most írjuk ki az oszlop tartalmat (ha van nagyítás, akkor egymás alá több byte, mind ugyanabba az oszlopba
				nagy_karakter2=nagy_karakter2 >> 8;   
			}
		}
    Wire.endTransmission();
	}
} 

Mennyire volt hasznos amit olvastál? Értékelés után szövegesen is leírhatod megjegyzéseidet és véleményedet!

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet! Ha kérdésed van, ne felejtsd el megadni az email címedet!