Pojďme programovat elektroniku | Arduino

Programování elektroniky: Jak funguje obrazovka na té nejnižší úrovni. Rozpohybujeme 64 světýlek

  • Oživíme v Arduinu surovou matici 64 světýlek LED
  • Bude to taková zmenšenina třeba vašeho televizoru
  • Vyzkoušíme si bodové a řádkové progresivní skenování

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.

2ed95951-6b1c-424a-9d5e-0563ef04877a0e583b73-b64a-4e2a-9a5d-2a96ec2395bdd7e195cc-3035-48d3-b877-20014b2ce7e2
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.

8d20534e-e17f-4650-a84c-14e0989158e6
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.

44167318-0e7b-4630-a078-f25c07b9ca8f
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.

1fa2ae28-f61e-4327-ac4d-e395e3c4e454
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é.

96bc71f3-c36f-420c-95f5-8692d6451ba5
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ů.

d88b6b3d-a234-480f-9fcd-b42c4efb954b
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.

de3f9aa1-a473-4f58-afe5-c55279826d1a
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.

c78db406-0fec-4db9-a1c4-784082f1222d
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.

7b2f804e-20a0-4983-8b23-bf7f1e49cae5
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.

55445426-681b-40db-b38a-9c897157477b
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. 

49febdbc-5c48-49c3-b057-9fcffbac70d4
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.

537b041b-2acb-4064-bfb4-253218ee224f
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!

0544ff36-b9a6-48bf-8ce2-5fdb42644afe
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.

942ecee0-e912-418c-8aa2-281c96c88278
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ý.

e0432f4c-9749-45b1-84ed-d847427e4465
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í.

ccf9e4fa-6177-45e7-8657-c98c4ff30af0
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í.

f004b6f2-c996-492e-8ad9-9d4b6d886a75
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
};
Diskuze (18) Další článek: Vy a počítač: Skládáte si vlastní PC, nebo kupujete již hotové?

Témata článku: , , , , , , , , , , , , ,