V dnešním pokračování našeho seriálu o programování elektroniky se vrátíme na úplný začátek, vyzkoušíme si totiž základní principy fungování nejrůznějších displejů a monitorů na primitivním maticovém displeji s 64 pixely.
Tento malý čtvereček s rozlišením 8×8 LED má ve sbírce snad každý kutil, veškerou zábavu ale zabíjí nějaká ta knihovna pro Arduino, a hlavně integrovaný obvod řadičů MAX7219/MAX7221, které z displeje dohromady udělají vlastně takový blackbox, jehož principům nemusíte vůbec rozumět.
Máme léto, čas teoretického bádání a prokrastinace, takže dnes se zaměříme právě na tuto magii pod kapotou, která se v různých obměnách nakonec odehrává i v televizorech, počítačových monitorech a dalších displejích.
Podívejte se na video se všemi ukázkami, které si vysvětlíme níže v článku:
8×8 světýlek
Drtivá většina e-shopů pro kutily nabízí 64pixelový maticový displej rovnou s řadičem za pár desítek korun, ale my jsme si přece řekli, že chceme jít až na dřeň. Surový modul tak pořídíte třeba na Aliexpressu za pár kaček, anebo v této rozdělané stavebnici za 60 Kč.
Když budeme chtít zapojit LED do obvodu s tužkovou baterií, stačí s kladným pólem baterie spojit anodu (+), se záporným katodu (-) a případně do obvodu sériově připojit ještě rezistor, aby nám protékající proud drobné světýlko okamžitě nespálil.
Maticový displej s 64 LED
Kdyby měl mít náš maticový displej s 64 světýlky stejné separátní zapojení pro každou z LED, muselo by z něj trčet 64 anod a 64 katod – 128 vodičů. To by sice ještě šlo, ale představte si, že byste chtěli takových modulů použít více, třeba deset, takže byste rázem potřebovali nějaký unikátní čip, který by nabízel 1 280 logických vývodů, na kterých byste nastavováním vysokých a nízkých stavů (tedy napětí) spínali a vypínali jednotlivé LED. Desítka maticových displejů dá přitom dohromady stále naprosto titěrné rozlišení 640 pixelů!
Sdílené řádky a sloupce
Namísto toho, aby měla každá LED oddělené napájení, svoje anody a katody na podobných maticových displejích sdílejí ve formě sloupců a řádků jako na schématu níže.
Schéma zapojení LED do sdílených napájecích řádků a sloupců. Sloupce a řádky se navzájem nedotýkají. Veškeré uzly v síti označují černé puntíky
Všimněte si, že takový modul má pouze 16 napájecích vývodů: 8 anod (+), které se starají o napájení celých řádků a 8 katod (-), které patří sloupcům a uzavírají elektrický obvod. Většina fyzických konstrukcí maticového displeje má vývody/piny v protilehlých stranách.
Pořadí napájecích pinů na displeji
Na nákresu hotového modulu výše vidíte tu nejtypičtější organizaci, přičemž řadu vývodů 1-8 označuje buď potisk na hraně displeje, anebo drobný plastový stupínek.
Jak rozsvítit světýlko na pozici 2;2
Pro zbytek článku předpokládejme souřadnicový systém XY, který bude začínat v levém horním rohu displeje na pozici 0;0 – to je tedy pozice první LED vlevo nahoře.
Pin 8 jsme připojili k 5V zdroji na desce Arduino a pin 4 jsme připojili k jeho systémové zemi GND. Tím se uzavřel obvod a na souřadnicích 2;2 se rozsvítilo světýlko
K rozsvícení světýlka na pozici 2;2 nebudeme potřebovat žádný počítač. Stačí dle schématu výše zjistit čísla pinů, které obsluhují 2. řádek (+) a 2. sloupec (-), připojíme k nim slabý zdroj napětí s malým průtokem elektrického proudu a světýlko se rozsvítí.
Jak rozsvítit sloupec 4 světýlek
Světýlko svítí, takže zkusme aktivovat další trojici LED pod ním – sloupec na souřadnicích 2;2 až 2;5. Stačí ke zdroji napětí připojit adekvátní trojici řádků. O uzavření obvodu se nám stále postará jeden jediný sloupec, do kterého jsou tyto LED svedené.
Aktivací vícero řádků rozsvítíme sloupec
Jak rozsvítit řádek 4 světýlek
Analogicky můžeme postupovat i pro rozsvícení 4 LED v řádku na souřadnicích 2;2 až 5;2. Tentokrát tedy připojíme ke zdroji napájení jen 2., řádek a k zápornému pólu zdroje, nebo k systémové zemi GND prototypovací desky, připojíme čtveřici odpovídajících sloupců.
Aktivací vícero sloupců rozsvítíme řádek
A teď zkusme rozsvítit rám čtverce
Doposud to fungovalo na jedničku, ale v dalším kroku už kvůli sdílení napájecích větví tvrdě narazíme. Představte si například, že bychom chtěli rozsvítit čtverec – ale bez výplně – jako na schématu níže.
Chceme na maticovém displeji vytvořit právě tento tvar
Když spojíme dva předchozí příklady pro rozsvícení sloupce a řádku, uzavřeme v tomto případě obvody nejen pro okrajové LED jako na obrázku výše, ale i pro všechny vnitřní světýlka, protože prostě sdílejí napájení.
Pokud se v tom už trošku ztrácíte, opět pomůže schéma níže, které ukazuje, co se v nitru vlastně stane.
Namísto okrajů se nám zobrazí plný čtvereček, protože vnitřní LED sdílejí napájení
Jak rozsvítit rám čtverce? Rychlým blikáním
Pokud nemůžeme kvůli sdíleným napájecím linkám v jednom kroku zobrazit libovolný obrazec na matici, musíme jej rozdělit do vícero kroků, které budeme zobrazovat jeden za druhým. Možných technik je celá řada, přičemž tou nejjednodušší je postupné spínání vždy jen jednoho bodu.
Namísto zobrazování celých struktur v každém kroku rozsvítíme jen jednu LED
Jelikož chceme zobrazit prázdný čtverec uprostřed displeje a známe souřadnice LED, které potřebujeme rozsvítit, jednu po druhé na prchlivý okamžik sepneme. Když to budeme opakovat stále dokola a dostatečně rychle, díky relativně pomalému lidskému zraku budeme vnímat spojitý obraz.
Lidský zrak je naštěstí pomalý, rychlé spínání jednotlivých LED obrazce proto bude náš mozek vnímat jako ucelený vjem
Anebo třeba spínáním celých řádků
Namísto spínání vždy jen jedné LED matice můžeme volit i jiné a časově úspornější techniky. Třeba bychom mohli v jednom kroku zobrazit vždy celý řádek matice. Tím dosáhneme i s jednoduchým 16MHz mikrokontrolerem ze základních stavebnic Arduino velmi vysokých obnovovacích frekvencí až v řádu kHz.
Stejný obrazec, ale tentokrát spínáme a vypínáme shora dolů celé řádky
K čemu je taková rychlost dobrá? Znamená to, že procesoru zbyde hromada prostředků na to, aby prováděl i další úkony, a zároveň budeme mít prostor k tomu, abychom do hry zapojovali další a další moduly a vytvářeli zobrazovače s větším rozlišením.
Tentokrát získáme vjem celého obrazce postupným rychlým spínáním celých řádků matice
Vyzkoušeli jsme si tedy dvě techniky tzv. progresivního (postupného) skenování, které připomínají jak fungování starého CRT monitoru, tak v druhé ukázce i těch novějších.
Současné displeje, monitory a televizory nedělají v základním principu v podstatě nic jiného, jen se musejí poprat s o mnoho řádů větším počtem drobných světýlek, o což se starají třeba speciální rychlé řádkové řadiče s vlastní pamětí.
Program pro Arduino a desku Arduino Mega
Byť je na GitHubu a dalších skladištích kódu nepřeberné množství knihoven pro práci s podobnými maticovými displeji, které se za nás postarají o matematiku a přesné časování spínání a vypínání LED, pokud chcete proniknout do tajů generování takového signálu, cesta je cíl a není nic zábavnějšího, než si to celé postavit na zelené louce a ano, znovu vynalézat kolo. Ale vaše unikátní kolo!
Schéma zapojení surového modulu s 64 LED bez vlastního řadiče a základní desky Arduino Mega, nebo jejího levnějšího asijského klonu
Jelikož bude malý čtvereček s 64 LED potřebovat 16 napájecích vodičů, jako prototypovací desku, ke které displej připojíme, jsme zvolili obří a elektricky dostatečně robustní Arduino Mega (respektive laciný asijský klon) s desítkami vývodů pro obecný digitální signál GPIO.
Primitivní příklad pro Arduino a rozsvícení LED na souřadnici 2;2 podle zapojení k desce Arduino na schématu výše:
void setup() {
pinMode(9, OUTPUT); // 2. radek, anoda +
pinMode(5, OUTPUT); // 2. sloupec, katoda -
// Rozsviceni LED na souradnicich 2;2
// z prvniho prikladu
digitalWrite(9, HIGH);
digitalWrite(5, LOW);
}
void loop() {;}
Zatímco napětí pro rozsvícení LED vytvoříme nastavením vysokého digitálního stavu na vybraném pinu, záporný pól vyrobíme nastavením nízkého logického stavu. Nastavováním kombinací vysokého a nízkého stavu na řádcích a sloupcích pak budeme řídit otevírání a uzavírání napájecích obvodů pro konkrétní LED.
Rozsvícení LED na souřadnicích 2;2 v praxi
Jelikož budeme světýlky ve skutečnosti blikat, takže v jeden okamžik bude svítit jen jedna (bodové progresivní skenování), nebo nejvýše 8 světýlek řádku (řádkové progresivní skenování), zátěž bude relativně nízká. Pro klid v duši a produkční verzi lze do linek napájejících anod (řádky displeje) připojit ještě slabé rezistory, které omezí protékající proud. Pro jednoduchost našeho příkladu se ale obejdeme i bez nich.
64bitová bitmapa
Maticový displej má 8 řádků a každý z nich má 8 světýlek LED, celý obrazec, který na něm budeme chtít vykreslit, si tak můžeme představit jako bitmapu s 1b hloubkou o velikosti 64 bitů – 8 bajtů. Bit s hodnotou 1 bude odpovídat rozsvícenému pixelu a bit s hodnotou 0 pak pixelu, který má zůstat zhasnutý.
Libovolný obrazec, který chceme promítnout na displej, se nám vejde do 8 bajtů
Bitmapu pro náš obrys čtverečku bychom tedy v C++ mohli vyjádřit třeba jako pole 8 osmibitových čísel bezznaménkového typu uint8_t, přičemž každé číslo bude představovat jeden řádek. Pokud se v tom už ztrácíte, schéma výše vám napoví.
Náš kýžený čtvereček v praxi! Obrazec se překresluje rychlostí několika set Hz, lidské oko proto nemá šanci rozpoznat, že ve skutečnosti spínáme a vypínáme jedno LED po druhé
Při vykreslování bitmapy na displej pak už jen stačí procházet bajt po bajtu a v každém z nich bit po bitu. Pokud bude mít některý z nich hodnotu 1, na jeho souřadnici na kratičký okamžik rozsvítíme LED. Když to budeme dělat stále dokola a velmi rychle po sobě, na displeji se ukáže obrázek.
Program pro vykreslení bitmapy metodou bodového progresivního skenování:
// Piny jednotlivych radku (anody +)
uint8_t anody[8] = {10, 15, 9, 13, 2, 8, 3, 6};
// Piny jednotlivych sloupcu (katody -)
uint8_t katody[8] = {14, 4, 5, 11, 7, 12, 16, 17};
// Doba v us, po kterou ma LED svitit
// Vyssi hodnota, vyssi jas
uint16_t JAS = 100;
// Bitmapa naseho ctverecku
// Pro nazornost v binarnim zapisu,
// aby byly videt jednotlive LED,
// ktere maji svitit
uint8_t obrazek[8] = {
0b00000000,
0b00000000,
0b00111100,
0b00100100,
0b00100100,
0b00111100,
0b00000000,
0b00000000
};
// Pomocna funkce pro zhasnuti vsech LED
void vycisti() {
for (uint8_t i = 0; i < 8; i++) {
digitalWrite(anody[i], LOW);
digitalWrite(katody[i], HIGH);
}
}
// Funkce pro rozsviceni konkretniho pixelu
// Nejprve zhasneme LED, ktere mohly svitit v predchozim kroku
// Na zaver pockame par mikrosekund, at LED vyzari dostatek svetla
void pixel(uint8_t x, uint8_t y) {
vycisti();
digitalWrite(anody[y], HIGH);
digitalWrite(katody[x], LOW);
delayMicroseconds(JAS);
}
// Funkce pro vykresleni bitmapy
// Projdeme jednotlive bajty radek po radku
// V kazdem bajtu pak projdeme vsechny bity predstavujici sloupce
// Pokud bude bit roven 1, rozsvitime patricnou LED pomoci funkce pixel
// V opacnem pripade se ujisitime, ze je dana LED zhasnuta
// Doba vykresleni bitmapy se lisi podle toho, kolik pixelu ma svitit,
// protoze volani a zpracovavani funkce pixel zabere nejaky cas
// Pro jednoduchost prikladu neresime konstantni FPS napric ruznymi bitmapami
void nakresliBitmapu(uint8_t* bitmapa) {
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
if (bitRead(bitmapa[y], 7 - x) == 1) {
pixel(x, y);
}
else {
digitalWrite(anody[y], LOW);
digitalWrite(katody[x], HIGH);
}
}
}
}
// Hlavni funkce setup, ktera se zpracuje na zacatku
void setup() {
// Nakonfigurujeme vsechny piny pro displej na vystup
// A nastavime na nich vychozi zhasnuty stav
for (uint8_t i = 0; i < 8; i++) {
pinMode(anody[i], OUTPUT);
pinMode(katody[i], OUTPUT);
digitalWrite(anody[i], LOW);
digitalWrite(katody[i], HIGH);
}
}
// V nekonecne smycce loop stale dokola prekreslujeme
// displej zvolenou bitmapou. Smycku loop nesmime prilis blokovat,
// aby se nam vykreslovani nezpomalilo a neprislo o synchronizaci
// V praxi se hodi interni asynchronni casovace cipu,
// ktere pro jednoduchost ukazky nepouzijeme
void loop() {
nakresliBitmapu(obrazek);
}
Kompletní program s bitmapami abecedy
Na úplná závěr dnešního článku ještě nesmíme zapomenout na kompletní program z úvodního videa. Stejným způsobem jako bitmapu pro čtvereček jsme totiž vytvořili bitmapy pro základní velká písmena a číslovky tabulky ASCII v souboru abeceda.h. Malá písmena tabulky jsme pak vyhradili pro uživatelské bitmapy. Takže třeba znak „a“ bude odpovídat bitmapě se smajlíkem 😊.
Náš program po zpuštění začne na displeji znak po znaku vykreslovat zprávu MAME RADI ZIVE.CZ abcdef, přičemž namísto malých znaků a, b, c, d, e, f vykreslí zmíněný smajlík a několik dalších obrázků.
Program zároveň bude poslouchat na sériové lince (115 200 b/s) na příchozí instrukce z PC zakončené zalomením řádku:
- *cislo = doba v milisekundách mezi vykreslováním pixelů. Tímto způsobem můžeme animaci zpomalit až na viditelné spínání a zhasínání jednotlivých LED
- /cislo = doba svítícího pixelu v mikrosekundách, jejíž změnou si můžeme pohrát s jasem
- #cislo = prodleva v milisekundách mezi jednotlivými vykreslovanými bitmapami. Tedy rychlost animace
- + = aktivace řádkového progresivního skenování
- - = aktivace bodového progresivního skenování
- libovolný jiný text = začneme jej vykreslovat na displeji
Takže když pošleme do řídícího čipu třeba instrukci *100, zvýšíme prodlevu mezi vykreslovanými pixely na 100 ms a místo spojitě vykreslované bitmapy uvidíme, jak se postupně a dostatečně pomalu spínají a vypínají její jednotlivé LED. Pomocí této instrukce lze krásně srovnat odlišný charakter vykreslování technikou bodového progresivního skenování a naopak výrazně rychlejšího řádkového progresivního vykreslování.
V sériové lince se nám do PC posílá statistika, co se zrovna děje
No, a kdybychom si v souboru abeceda.h vytvořili další bitmapy, které tvoří třeba části animace, kterou chceme přehrávat rychle po sobě, tempo zhruba na 10 Hz nastavíme zase příkazem #100.
O technikách vykreslování obrazu na displeje bychom toho mohli napsat ještě stohy, v nejlepším je ale třeba přestat. Níže najdete kompletní kód, se kterým si můžete libovolně hrát.
Není ani zdaleka nejefektivnější – pro jednoduchost a srozumitelnost pro začátečníky například nepoužíváme rychlé změny stavů pomocí přímého přístupu k registrům čipu a stejně tak nepoužíváme přesnější časování událostí pomocí dedikovaných interních hodin –, jako základní demonstrace principu to ale bohatě stačí a rychlost překreslování bitmap může dosahovat v našem provedení i tak až několika set Hz.
Pozor, náš program nepoužívá konstantní FPS. Obnovovací frekvence se liší podle toho, z kolika svítících pixelů se skládá každá z bitmap. Plný tvar se všemi aktivními LED proto budeme vykreslovat nejdelší dobu, zatímco bitmapu s jedním jediným svítícím pixelem zdaleka nejrychleji. I zde je hlavním důvodem co nejjednodušší kód bez řízení konstantní periody zhasnutého a rozsvíceného pixelu.
Zdrojový kód hlavního programu:
#include "abeceda.h"
/*
RADKY: anody +
SLOUPCE: katody -
R/S S8 S7 R2 S1 R4 S6 S4 R1
PIN 16 15 14 13 12 11 10 9
| | | | | | | |
* * * * * * * * *
* * * * * * * * *
* * * * * * * * *
* * * * * * * * *
* * * * * * * * *
* * * * * * * * *
* * * * * * * * *
* * * * * * * * *
| | | | | | | |
R/S R5 R7 S2 S3 R8 S5 R6 R3
PIN 1 2 3 4 5 6 7 8
RADKY A PINY MODULU:
R1 9
R2 14
R3 8
R4 12
R5 1
R6 7
R7 2
R8 5
SLOUPCE A PINY MODULU:
S1 13
S2 3
S3 4
S4 10
S5 6
S6 11
S7 15
S8 16
PINY MODULU PRIPOJIM NA DESKU ARDUINO MEGA
POCINAJE JEJIM PINEM 2, TAKZE PLATI:
PIN ARDUINO MEGA = PIN MODULU + 1
*/
uint8_t anody[8] = {10, 15, 9, 13, 2, 8, 3, 6};
uint8_t katody[8] = {14, 4, 5, 11, 7, 12, 16, 17};
// Pomocne promenne pro chod a casovani programu
uint8_t id = 0;
uint32_t znacka = 0;
uint32_t cas_pixel = 0;
uint32_t cas_pixel_radek;
uint32_t cas_bitmapa = 0;
uint16_t T = 0;
uint16_t JAS = 10;
uint16_t RYCHLOST = 500;
uint8_t zprava[100] = {0};
uint8_t zprava_delka = 0;
bool radkove = false;
void setup() {
// Nastartujeme seriovou linku do PC rychlosti 115 200 b/s
// Budeme s ni moci ovladat displej pomoci instrukci ukoncenych zalomenim radku:
// *cislo = doba v ms mezi vykreslovanim pixelu. Muzeme tim animaci zpomalit pro studijni ucely
// /cislo = doba sviticho pixelu v us. Zvysenim hodnoty zvysime jas
// #cislo = prodleva v ms mezi jednotlivymi bitmapami, tedy rychlost animace
// + aktivace radkoveho progresivniho skenovani
// - aktivace bodoveho progresivniho skenovani
// libovony jiny text = text, ktery se zacne zobrazovat na displeji
Serial.begin(115200);
// Vychozi nastaveni pinu displeje
for (uint8_t i = 0; i < 8; i++) {
pinMode(anody[i], OUTPUT);
pinMode(katody[i], OUTPUT);
digitalWrite(anody[i], LOW);
digitalWrite(katody[i], HIGH);
}
// Prelozime na bitmapy znaku vychozi zpravu,
// ktera se zacne zobrazovat znak po znaku na displeji
char uvodni_zprava[] = " MAME RADI ZIVE.CZ abcdef ";
kresliText(uvodni_zprava, strlen(uvodni_zprava));
}
// Funkce pro smazani displeje
void vycisti() {
for (uint8_t i = 0; i < 8; i++) {
digitalWrite(anody[i], LOW);
digitalWrite(katody[i], HIGH);
}
}
// Funkce pro prevod ASCII kodu na index bitmapy v poli v abeceda.h
uint8_t asciiNaBitmapu(char znak) {
uint8_t index = (uint8_t)znak;
Serial.print("Prevadim vstupni znak: "); Serial.print(znak, HEX); Serial.print(": ");
// Pro tyto znaky ASCII z tohoto rozsahu mam vlastni bitmapy
if (index >= zacatek_ascii && index < (zacatek_ascii + sizeof(bitmapy) / sizeof(bitmapy[0]))) {
Serial.println(index - zacatek_ascii);
return (index - zacatek_ascii);
}
else {
// Jiny kod ASCII, pro ktery nemam bitmapu, takze vratim mezeru/prazdne misto
Serial.println(0);
return 0;
}
}
// Funkce pro prevod textove zpravy v ASCII na indexy bitmap
// v nasi abecede bitmap, kterou mame v souboru abeceda.h
// Znaky A-Z, 0-9 a nektere specialni vykreslime standardne,
// a-z nam pak slouzi pro vlastni non-ASCII bitymapy
void kresliText(char *txt, uint8_t delka) {
Serial.print("Text: ");
Serial.println(txt);
for (uint8_t i = 0; i < delka; i++) {
zprava[i] = asciiNaBitmapu(txt[i]);
}
zprava_delka = delka;
Serial.print("Delka zpravy: "); Serial.println(zprava_delka);
Serial.print("Bitmapy: ");
for (uint8_t i = 0; i < zprava_delka; i++) {
Serial.print(zprava[i]); Serial.print(" ");
}
Serial.println("");
}
// Funkce pro vykresleni pixelu
void pixel(uint8_t x, uint8_t y, uint16_t prodleva) {
uint32_t t = micros();
vycisti();
digitalWrite(anody[y], HIGH);
digitalWrite(katody[x], LOW);
delayMicroseconds(JAS);
cas_pixel = micros() - t;
delay(prodleva);
}
// Funkce pro vykresleni celeho radku
void pixel_radek(uint8_t radek, uint8_t y, uint16_t prodleva) {
uint32_t t = micros();
vycisti();
for (uint8_t x = 0; x < 8; x++) {
if (bitRead(radek, 7 - x) == 1) digitalWrite(katody[x], LOW);
else digitalWrite(katody[x], HIGH);
}
digitalWrite(anody[y], HIGH);
delayMicroseconds(JAS);
cas_pixel_radek = micros() - t;
delay(prodleva);
}
// Vykresleni bitmapy metodou bodoveho progresivniho skenovani
void nakresliBitmapu(uint8_t* bitmapa) {
uint32_t t = micros();
for (uint8_t y = 0; y < 8; y++) {
for (uint8_t x = 0; x < 8; x++) {
if (bitRead(bitmapa[y], 7 - x) == 1) {
pixel(x, y, T);
}
else {
digitalWrite(anody[y], LOW);
digitalWrite(katody[x], HIGH);
}
}
}
cas_bitmapa = micros() - t;
}
// Vykresleni bitmapy metodou radkoveho progresivniho skenovani
void nakresliBitmapuRadkove(uint8_t* bitmapa) {
uint32_t t = micros();
for (uint8_t y = 0; y < 8; y++) {
pixel_radek(bitmapa[y], y, T);
}
cas_bitmapa = micros() - t;
}
// Smycka loop musi co nejmene blokovat,
// volame v ni totiz funkci pro prekresleni displeje
// Proto i vypis do seriove linky zpusobi kazdou sekundu
// drobne probliknuti displeje
void loop() {
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
// Prikaz: *cislo
// Instrukce pro nastaveni prodlevy ve vykreslovani v milisekundach
// Slouzi pro umele zpomalovani vykreslovani pro demonstracni ucely
// Uvidite, jak se svetylka zapinaji a vypinaji
if (cmd[0] == '*') {
T = cmd.substring(1).toInt();
Serial.print("Nastavuji prodlevu mezi pixely na ");
Serial.print(T);
Serial.println(" ms");
}
// Prikaz: /cislo
// Instrukce pro nastaveni doby sviticiho pixelu v mikrosekundach
// Vyssi cas, delsi doba svitu, vyssi jas
else if (cmd[0] == '/') {
JAS = cmd.substring(1).toInt();
Serial.print("Nastavuji delku/jas pixelu na ");
Serial.print(JAS);
Serial.println(" us");
}
// Prikaz: #cislo
// Instrukce pro rychlost stridani bitmap
else if (cmd[0] == '#') {
RYCHLOST = cmd.substring(1).toInt();
Serial.print("Nastavuji rychlost znaku na ");
Serial.print(RYCHLOST);
Serial.println(" ms");
}
// Prikaz: +
// Instrukce pro aktivaci radkoveho progresivniho skenovani
else if (cmd[0] == '+') {
Serial.println("Aktivuji radkove vykreslovani");
radkove = true;
}
// Prikaz: -
// Instrukce pro aktivaci bodoveho progresivniho vykreslovani
else if (cmd[0] == '-') {
Serial.println("Aktivuji bodove vykreslovani");
radkove = false;
}
// Prikaz: Jakykoliv jiny text po seriove lince
// Vyrobime z jeho znaku bitmapy a ty budeme ve smyce zobrazovat na displeji
// A-Z zobrazime jako znaky, a-z jako vlastni non-ASCII bitmapy
// Viz abeceda.h s definicemi
else {
cmd.trim();
kresliText(cmd.c_str(), cmd.length());
}
}
// Pokud uplyne cas RYCHLOST (ms), vypis statistiku do seriove linky
// a nastav novy index bitmapy, ktera se ma vykreslovat
if (millis() - znacka > RYCHLOST) {
znacka = millis();
Serial.print("ID: "); Serial.print(id); Serial.print(' ');
Serial.print("Znak: "); Serial.print(zprava[id]); Serial.print(' ');
if (radkove) {
Serial.print("radek: ");
Serial.print(cas_pixel_radek);
Serial.print("us ");
}
else {
Serial.print("pixel: ");
Serial.print(cas_pixel);
Serial.print("us ");
}
Serial.print("bitmapa: "); Serial.print(cas_bitmapa / 1000.0f); Serial.print("ms ");
Serial.print("frekvence: "); Serial.print(round(1000.0f / (cas_bitmapa / 1000.0f))); Serial.println("fps");
if (id >= zprava_delka - 1) id = 0; else id++;
}
// Pokud mame co vykreslovat, podle rezimu vykreslovani (bodovy/radkovy)
// to vykresli
if (zprava_delka > 0) {
if (radkove) nakresliBitmapuRadkove(bitmapy[zprava[id]]);
else nakresliBitmapu(bitmapy[zprava[id]]);
}
}
Zdrojový kód s bitmapami abeceda.h:
// Pracujeme jen s ASCII kody 32-90
// protoze pro ostatni (zatim) nemame bitmapy
uint8_t zacatek_ascii = 32;
uint8_t bitmapy[74][8] = {
// ZACATEK ASCII TABULKY, JEN KODY 32-90
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // MEZERA
{0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18}, // !
{0x00, 0x00, 0x24, 0x24, 0x24, 0x24, 0x00, 0x00}, // "
{0x00, 0x42, 0xff, 0x42, 0x42, 0xff, 0x42, 0x00}, // #
{0x3e, 0x50, 0x50, 0x3c, 0x12, 0x12, 0x12, 0x7c}, // $
{0x01, 0x62, 0x64, 0x08, 0x10, 0x26, 0x46, 0x80}, // %
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // & - zatim nenakresleny
{0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00}, // '
{0x08, 0x10, 0x20, 0x20, 0x20, 0x20, 0x10, 0x08}, // (
{0x10, 0x08, 0x04, 0x04, 0x04, 0x04, 0x08, 0x10}, // )
{0x00, 0x92, 0x54, 0x38, 0xff, 0x38, 0x54, 0x92}, // *
{0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00}, // +
{0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00}, // ,
{0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00}, // -
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00}, // .
{0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00}, // /
{0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c}, // 0
{0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x08, 0x3e}, // 1
{0x3c, 0x42, 0x42, 0x04, 0x08, 0x10, 0x60, 0x7e}, // 2
{0x3c, 0x42, 0x02, 0x1c, 0x02, 0x02, 0x42, 0x3c}, // 3
{0x04, 0x0c, 0x14, 0x24, 0x44, 0x7e, 0x04, 0x04}, // 4
{0x7c, 0x40, 0x40, 0x3c, 0x02, 0x02, 0x04, 0x78}, // 5
{0x3c, 0x40, 0x40, 0x7c, 0x42, 0x42, 0x42, 0x3c}, // 6
{0x7e, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40}, // 7
{0x3c, 0x42, 0x42, 0x3c, 0x42, 0x42, 0x42, 0x3c}, // 8
{0x3c, 0x42, 0x42, 0x42, 0x3e, 0x02, 0x02, 0x3c}, // 9
{0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00}, // :
{0x00, 0x00, 0x10, 0x00, 0x10, 0x10, 0x20, 0x00}, // ;
{0x00, 0x00, 0x08, 0x10, 0x20, 0x10, 0x08, 0x00}, // <
{0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00}, // =
{0x00, 0x00, 0x10, 0x08, 0x04, 0x08, 0x10, 0x00}, // >
{0x7c, 0x02, 0x02, 0x7c, 0x80, 0x7c, 0x00, 0x18}, // ?
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // @ - zatim nenakresleny
{0x3c, 0x42, 0x81, 0x81, 0xff, 0x81, 0x81, 0x81}, // A
{0xfe, 0x81, 0x81, 0xfe, 0x81, 0x81, 0x81, 0xfe}, // B
{0x3f, 0x40, 0x80, 0x80, 0x80, 0x80, 0x40, 0x3f}, // C
{0xfc, 0x82, 0x81, 0x81, 0x81, 0x81, 0x82, 0xfc}, // D
{0xff, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0xff}, // E
{0xff, 0x80, 0x80, 0xff, 0x80, 0x80, 0x80, 0x80}, // F
{0x3c, 0x42, 0x81, 0x80, 0x9f, 0x81, 0x42, 0x3c}, // G
{0x81, 0x81, 0x81, 0xff, 0x81, 0x81, 0x81, 0x81}, // H
{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, // I
{0x01, 0x01, 0x01, 0x01, 0x81, 0x81, 0x42, 0x3c}, // J
{0x42, 0x44, 0x48, 0x50, 0x68, 0x44, 0x42, 0x41}, // K
{0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c}, // L
{0xc6, 0xaa, 0x92, 0x82, 0x82, 0x82, 0x82, 0x82}, // M
{0x81, 0xc1, 0xa1, 0x91, 0x89, 0x85, 0x83, 0x81}, // N
{0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c}, // O
{0x7c, 0x42, 0x42, 0x42, 0x7c, 0x40, 0x40, 0x40}, // P
{0x3c, 0x42, 0x42, 0x42, 0x52, 0x4a, 0x46, 0x3e}, // Q
{0x7c, 0x42, 0x42, 0x42, 0x7c, 0x48, 0x44, 0x42}, // R
{0x3e, 0x40, 0x40, 0x3c, 0x02, 0x02, 0x02, 0x7c}, // S
{0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}, // T
{0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c}, // U
{0x42, 0x42, 0x42, 0x42, 0x42, 0x24, 0x24, 0x18}, // V
{0x82, 0x82, 0x82, 0x82, 0x82, 0x54, 0x54, 0x38}, // W
{0x42, 0x42, 0x24, 0x18, 0x24, 0x42, 0x42, 0x42}, // X
{0x82, 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10}, // Y
{0x7e, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x7e}, // Z
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // [ - zatim nenakresleny
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // \ - zatim nenakresleny
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ] - zatim nenakresleny
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ^ - zatim nenakresleny
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // _ - zatim nenakresleny
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ` - zatim nenakresleny
// VLASTNI IKONY NAMAPOVANE NA ASCII KOD 97 A DAL
// Tyto ASCII kody odpovidaji malym znakum a-z
{0x3c, 0x42, 0x81, 0xa5, 0x81, 0x99, 0x42, 0x3c}, // :-)
{0x7e, 0xbd, 0xdb, 0xe7, 0xe7, 0xdb, 0xbd, 0x7e}, // obrazek1
{0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81}, // obrazek2
{0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55}, // obrazek3
{0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa}, // obrazek4
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} // plny tvar
};