← zpět na zápisky z Arduino projektů
OLED
Naše první pokusy s OLED displejem. Já vím, zkratka OLED už sama o sobě v sobě obsahuje slovo displej, ale stejně tomu tak všichni říkají :-) OLED mají nízkou spotřebu a dobrý zobrazovací kontrast (svítí jen to, co svítit musí). Tak jsme taky nějaké chtěli vyzkoušet.
Co je potřeba umět
Rozhodně se hodí vědět, jaký přesně displej vlastně držím v ruce. To usnadní jeho ovládání :-) Jinak nejsou potřeba asi žádné speciální znalosti.
Hardware
- OLED displej s I2C, např. SSD1306 (Aliexpress)
Jak to funguje
OLED displej, který jsme využili, má úhlopříčku 0,96” a rozlišení 128×64 pixelů. Připojili jsme ho přes rozhraní I2C. To komunikuje na 2 pinech SDA
(data) a SCL/SCK
(clock). Tyto má Arduino Uno schováno na analogových pinech A4 (SDA)
a A5 (SCL/SCK)
(zdroj).
K ovládání displeje se dále hodí knihovna u8g. Asi nejtěžším úkolem bylo zjistit, co přesně máme za displej a jakým konstruktorem knihovnu inicializovat. Nutno říci, že na displeji samotném jsme příliš vodítek nenašli.
Někdy můžete natrefit na displej, který je dvoubarevný. Nejde většinou o plnohodnotnou dvoubarevnost, ale jistá část displeje se zobrazuje vždy pevně jednou barvou a zbytek druhou. Barvu tak nenastavujete při vykreslování jako parametr, ale volíte ji umístěním na displeji.
Texty se na displeji vypisují metodou drawStr. Fonty se nastavují metodou setFont a je z čeho vybírat.
Následně děti projevily velký zájem zobrazovat na displej i něco jiného než jen texty různých velikostí. Postup je následující:
- Namalovat obrázek ve svém oblíbeném grafickém programu. Je dobré si nastavit velikost papíru na rozlišení displeje (např. 128×64), abychom se pak vešli. Pokud je displej jednobarevný či falešně dvoubarevný, je dobré kreslit pouze černou na bílem podkladu. Výsledný obrázek ořezat na minimální velikost.
- Pomocí programu pro převod bitmapy do kódu převést obrázek. Převod je vlastně pouhý převod černých pixelů na zápis v šestnáctkové soustavě (hexa) do zdrojového kódu tak, aby bylo možné ho nahrát do displeje.
- K převodu jsme využili program Image2Code napsaný v Javě (stačí pouze soubor Image2Code.jar, neinstaluje se, stačí spustit). Pokud Javu na počítači zatím nemáte, je potřeba doinstalovat. Pro Linux třeba takto:
sudo apt-get install default-jre
Program se pak spouští z příkazové řádky
java -jar Image2Code.jar
Ale je samozřejmě možné využít i jakýkoli jiný program, např. LCD image converter, který běhá na Windows, ale je možné ho zkompilovat i pro Linux - a celkově vypadá vymazleně.
Výsledný hexa kód nakopírovat do zdrojového souboru pro Arduino.
const uint8_t veselySmajl[] PROGMEM = {
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x80,
...
...
...
};
Klíčové slovo PROGMEM
zde zařídí, že celá bitmapa nebude uložena v operační paměti Arduina - té máme totiž obvykle velmi málo (Uno R3 = 2 KiB), ale protože se nebude v programu měnit (je konstantní), může být umístěna do tzv. flash paměti, kde leží i náš program (Uno R3 = 32 KiB). Více o rozdělení a druzích pamětí si lze přečíst zde, práce s PROGMEM je vysvětlena zde.
Ještě potřebujeme vědět, jaké má obrázek rozměry. Šířku vydělíme osmi a zaokrouhlíme nahoru (= počet bajtů, které jsou na řádku), výšku pak necháme, jak je. Pro vykreslení slouží funkce drawBitmapP().
//sirka obrazku = 65 -> 9 bajtu na radek
// vyska obrazku = 57
drawBitmapP(5, 5, 9, 57, veselySmajl);
Možná někoho napadne, proč není potřeba následně ve volání drawBitmapP
kopírovat data bitmapy z flash paměti do operační (pomocí nějaké z metod rozhraní pgmspace), když to návody u klíčového slova PROGMEM
ukazují. Je to proto, že knihovna u8g již předpokládá, že bitmapu ve flash paměti máme.
Ukázka z kódu knihovny v těle drawBitmapP
u8g_Draw8Pixel(u8g, x, y, 0, u8g_pgm_read(bitmap));
Knihovna u8g využívá koncept redraw - je potřeba neustále obnovovat stav displeje opakovaným překreslováním.
void loop(void) {
u8g.firstPage();
do {
draw();
} while(u8g.nextPage());
// po nejake dobe prekresli displej
delay(1000);
}
Schéma zapojení
Program s textem
#include "U8glib.h"
// inicializace OLED
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);
void setup() {
// nic
}
// vypise vysledek na displeji
void vypis() {
u8g.setFont(u8g_font_fub25n);
u8g.drawStr(40, 30, "BAF!");
u8g.setFont(u8g_font_unifont);
u8g.drawStr(45, 50, "strasidlo");
}
void loop() {
u8g.firstPage();
do {
vypis();
} while (u8g.nextPage());
delay(1000);
}
Program s obrázkem
#include "U8glib.h"
// inicializace OLED
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);
const uint8_t vesely[] PROGMEM = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x7f,0xff,0x80,0x0,0x0,0x0,
0x0,0x0,0x7,0xff,0xff,0xf8,0x0,0x0,0x0,
0x0,0x0,0x1f,0x80,0x0,0x7e,0x0,0x0,0x0,
0x0,0x0,0x7c,0x0,0x0,0xf,0x80,0x0,0x0,
0x0,0x1,0xe0,0x0,0x0,0x1,0xe0,0x0,0x0,
0x0,0x3,0x80,0x0,0x0,0x0,0x70,0x0,0x0,
0x0,0xf,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,
0x0,0x1c,0x0,0x0,0x0,0x0,0xe,0x0,0x0,
0x0,0x38,0x0,0x0,0x0,0x0,0x7,0x0,0x0,
0x0,0x70,0x0,0x0,0x0,0x0,0x3,0x80,0x0,
0x0,0xe0,0x0,0x0,0x0,0x0,0x1,0xc0,0x0,
0x1,0xc0,0x0,0x0,0x0,0x0,0x0,0xe0,0x0,
0x3,0x80,0x0,0x0,0x0,0x0,0x0,0x70,0x0,
0x3,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,
0x6,0x0,0x7,0x80,0x0,0x3c,0x0,0x18,0x0,
0x6,0x0,0x1f,0xe0,0x0,0xff,0x0,0x18,0x0,
0xc,0x0,0x3f,0xf0,0x1,0xff,0x80,0xc,0x0,
0xc,0x0,0x70,0x38,0x3,0x81,0xc0,0xc,0x0,
0x18,0x0,0xe0,0x1c,0x7,0x0,0xe0,0x6,0x0,
0x18,0x0,0xe0,0x1c,0x7,0x0,0xe0,0x6,0x0,
0x18,0x0,0xe3,0x1c,0x7,0x18,0xe0,0x6,0x0,
0x30,0x0,0xe7,0x9c,0x7,0x3c,0xe0,0x3,0x0,
0x30,0x0,0xe7,0x9c,0x7,0x3c,0xe0,0x3,0x0,
0x30,0x0,0xe7,0x9c,0x7,0x3c,0xe0,0x3,0x0,
0x30,0x0,0xe3,0x1c,0x7,0x18,0xe0,0x3,0x0,
0x30,0x0,0xe0,0x1c,0x7,0x0,0xe0,0x3,0x0,
0x30,0x0,0xe0,0x1c,0x7,0x0,0xe0,0x3,0x0,
0x30,0x0,0x70,0x38,0x3,0x81,0xc0,0x3,0x0,
0x30,0x0,0x3f,0xf0,0x1,0xff,0x80,0x3,0x0,
0x30,0x10,0x1f,0xe0,0x0,0xff,0x0,0x3,0x0,
0x30,0x18,0x7,0x80,0x0,0x3c,0x0,0x3,0x0,
0x30,0x18,0x0,0x0,0x0,0x0,0x0,0x3,0x0,
0x30,0x1c,0x0,0x0,0x0,0x0,0x0,0x3,0x0,
0x30,0x1c,0x0,0x0,0x0,0x0,0x0,0x3,0x0,
0x18,0xe,0x0,0x0,0x0,0x0,0x0,0x6,0x0,
0x18,0xf,0x0,0x0,0x0,0x0,0x0,0x6,0x0,
0x18,0x7,0x80,0x0,0x0,0x0,0x0,0x6,0x0,
0xc,0x3,0xc0,0x0,0x0,0x0,0x70,0xc,0x0,
0xc,0x1,0xe0,0x0,0x0,0x0,0xf0,0xc,0x0,
0x6,0x0,0xf8,0x0,0x0,0x1,0xe0,0x18,0x0,
0x6,0x0,0x7f,0x0,0x0,0x3,0xc0,0x18,0x0,
0x3,0x0,0x1f,0x80,0x0,0x7,0x80,0x30,0x0,
0x3,0x80,0x7,0xe0,0x0,0x1f,0x0,0x70,0x0,
0x1,0xc0,0x1,0xfc,0x0,0xfe,0x0,0xe0,0x0,
0x0,0xe0,0x0,0x7f,0xff,0xf8,0x1,0xc0,0x0,
0x0,0x70,0x0,0x1f,0xff,0xe0,0x3,0x80,0x0,
0x0,0x38,0x0,0x0,0xfe,0x0,0x7,0x0,0x0,
0x0,0x1c,0x0,0x0,0x0,0x0,0xe,0x0,0x0,
0x0,0xf,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,
0x0,0x3,0x80,0x0,0x0,0x0,0x70,0x0,0x0,
0x0,0x1,0xe0,0x0,0x0,0x1,0xe0,0x0,0x0,
0x0,0x0,0x7c,0x0,0x0,0xf,0x80,0x0,0x0,
0x0,0x0,0x1f,0x80,0x0,0x7e,0x0,0x0,0x0,
0x0,0x0,0x7,0xff,0xff,0xf8,0x0,0x0,0x0,
0x0,0x0,0x0,0x7f,0xff,0x80,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0
};
int sirka_displeje = 128;
int vyska_displeje = 64;
int sirka_obrazku = 65;
int vyska_obrazku = 57;
// vycentrovane souradnice pro obrazek
int x = (sirka_displeje - sirka_obrazku) / 2;
int y = (vyska_displeje - vyska_obrazku) / 2;
void setup() {
// nic
}
// vypise vysledek na displeji
void vykresli() {
//x = x souradnice, kde zacne kreslit obrazek (0;0 - levy horni roh)
//y = y souradnice, kde zacne kreslit obrazek (0;0 - levy horni roh)
//pocet bajtu na sirku obrazku
//vyska obrazku
//obrazek
int pocet_bajtu_sirka_obrazku = (int)(sirka_obrazku / 8.0 + 0.5);
u8g.drawBitmapP(x, y, pocet_bajtu_sirka_obrazku, vyska_obrazku, vesely);
}
void loop() {
u8g.firstPage();
do {
vykresli();
} while (u8g.nextPage());
delay(1000);
}
Možná vylepšení
- Jakékoliv hrátky s displejem. Kombinace s ručními blikači, automatickými blikači, apod.
- Je možné vytvořit jednoduché hry, přeci jen OLED displej dává poněkud sofistikovanější zobrazovací možnosti. Spolu s tlačítky je pak možné vyhodnocovat odpovědi hráče nebo hru přímo ovládat.
Poznatky
- Nejvíce nás potrápila detekce displeje, pak správný konstruktor, nakonec zapojení na SDA/SCK na Arduinu (nevěděl jsem, že jsou na pinech A4/A5). Ovládání přes knihovnu u8g bylo naopak už docela lehké. Pak jsem se potrápil při vysvětlování
PROGMEM
, ale je to zase hezká příležitost jak objasnit dětem trochu paměťové uspořádání Arduina. - Náš OLED displej je sice maličký, ale je to displej a to dává všem projektům úplně jiný rozměr.
- Rozhraní
I2C
není zrovna nejrychlejší (400 kHz), chtělo by toSPI
(až 20 MHz), ale to náš konkrétní displej zdá se neumí. Takže rychlé animace asi nebudou.