8×8 LED matice s MAX7219 a MicroPython
Každý bastlíř má radost, když jeho projekt pípá, svítí nebo dokonce bliká. Dnes se proto podíváme právě na trochu toho „blikání“. Naším cílem bude oblíbený zobrazovací modul s LED maticí 8×8 a řadičem MAX7219. A když říkáme modul, myslíme tím kompletní funkční celek, nikoliv jen samotnou LED matici, která se používá i samostatně.
LED matice 8×8
LED matice 8×8 se skládá ze 64 světelných diod uspořádaných do osmi řádků a osmi sloupců (obr. č. 1). Každá LED leží na průsečíku jednoho řádku a jednoho sloupce. Nastavíme-li příslušný řádek do logické úrovně HIGH a odpovídající sloupec do úrovně LOW, LED na jejich průsečíku se zapojí v propustném směru a rozsvítí se. Sláva – právě jsme rozsvítili první bod našeho zobrazovače.
Stejným principem lze postupně vytvářet libovolný obrazec, který se do matice 8×8 vejde. Stačí vhodně nastavovat logické úrovně jednotlivých řádků a sloupců.
Obrázek č. 1 – schéma zapojení LED v LED matici 8×8
Možná vás napadlo, že rozsvěcet celý obraz po jednotlivých LED by bylo poměrně pracné. Naštěstí existuje chytřejší způsob. Místo jednotlivých bodů můžeme zobrazovač řídit po celých řádcích.
Chceme-li například rozsvítit všechny LED v prvním řádku, nastavíme první řádek do úrovně HIGH a všechny sloupce do úrovně LOW. Potřebujeme-li v tomto řádku rozsvítit pouze druhou, třetí a sedmou LED, ponecháme řádek opět v úrovni HIGH a do úrovně LOW nastavíme pouze sloupce 2, 3 a 7.
V každém okamžiku tedy stačí aktivovat jeden z osmi řádků a současně nastavit odpovídající kombinaci sloupců.
Jenže právě tady narazíme na dva praktické problémy.
ALE! Je tu ALE…
- Jakmile aktivujeme jeden řádek, všechny ostatní musíme vypnout. Jinak by se rozsvítily LED podle právě nastavených sloupců i v ostatních řádcích. Řádky je proto nutné přepínat velmi rychle za sebou. Pokud je obnovovací frekvence dostatečně vysoká, lidské oko blikání nevnímá a obraz působí jako trvale rozsvícený. To ale znamená, že se mikrokontrolér musí o zobrazovač neustále starat.
- Osm vodičů pro řádky a osm vodičů pro sloupce. To už je pořádná „drátařina“. A zbydou nám na modulu ESP32 vůbec ještě nějaké volné piny pro další periferie?
Je tedy zřejmé, že přímé řízení samotné LED matice není příliš praktické. Vyžadovalo by poměrně složité zapojení i neustálé obnovování obrazu. Nabízí se proto použít specializovaný integrovaný obvod, který tuto práci převezme za mikrokontrolér. Přesně k tomu slouží obvod MAX7219, se kterým se seznámíme v následující části.
Modul zobrazovače LED 8x8 s MAX7219
Přímé řízení LED matice není zrovna nejšťastnější řešení. Naštěstí existuje integrovaný obvod, který celou zobrazovací režii převezme za mikrokontrolér. Jedním z nejrozšířenějších řešení je právě MAX7219.
MAX7219 je specializovaný obvod určený pro řízení LED zobrazovačů. Dokáže obsluhovat nejen sedmisegmentové displeje, ale také LED matice 8×8, se kterými budeme v tomto článku pracovat. Mikrokontrolér s ním komunikuje po sériovém rozhraní, takže místo šestnácti řídicích vodičů nám stačí pouhé tři signály.
Klíčové vlastnosti obvodu MAX7219
- určen pro řízení LED zobrazovačů prostřednictvím sériového rozhraní SPI (až 10 MHz),
- napájecí napětí 4,0 až 5,5 V (běžně 5 V),
- nastavitelný jas LED v 16 úrovních,
- režim úsporného vypnutí (Shutdown),
- automatické multiplexní řízení zobrazovače,
- maximální proud jednoho segmentového výstupu 100 mA,
- maximální proud zemnicího vývodu 500 mA.
Největší výhodou MAX7219 je, že se stará o multiplexní řízení LED matice zcela samostatně. Mikrokontrolér pouze odešle data, zatímco obvod zajistí jejich průběžné zobrazování. Nemusíme tedy řešit rychlé přepínání řádků ani neustálé obnovování obrazu.
Připojovací piny modulu
Moduly s obvodem MAX7219 bývají obvykle vybaveny dvěma pětipinovými konektory – jedním vstupním a druhým výstupním (obr. 2). Díky tomu lze jednotlivé moduly snadno řetězit za sebe.
Význam jednotlivých pinů je následující:
VCC– napájecí napětí modulu (obvykle +5 V).GND– společná zem.DIN(Data In) – vstup sériových dat z mikrokontroléru.CS(Chip Select, někdy takéLOAD) – aktivace přenosu dat do obvodu.CLK(Clock) – hodinový signál synchronizující přenos dat.
Obrázek č. 2 – rozložení pinů modulu MAX7219
Možná vás při pohledu na obrázek č. 2 napadlo, že jsme celou dobu mluvili o modulu MAX7219, ale na obrázku je vidět pouze LED matice. Ve skutečnosti je integrovaný obvod MAX7219 (u originálních modulů!) umístěn na spodní straně desky přímo pod LED maticí. Díky použití SMD součástek je celý modul velmi kompaktní – viz obrázek č. 3.
Na trhu ale najdeme i generické moduly, kde je obvod MAX7219 osazen v klasickém pouzdře DIL vedle LED matice, takže je dobře viditelný. Jak dále uvidíme, ještě se tímto problémem budeme zabývat.
Obrázek č. 3 – originální modul MAX7219 koncipovaný přímo pod LED zobrazovač
Kaskádové zapojení
Jednou z největších předností obvodu MAX7219 je možnost snadného kaskádového zapojení. Jednotlivé moduly lze jednoduše propojit za sebe a všechny potom řídit jediným mikrokontrolérem.
Princip je velmi jednoduchý. Datový výstup DOUT prvního obvodu se připojí na datový vstup DIN následujícího obvodu. Mikrokontrolér odesílá data pouze do prvního modulu a každý MAX7219 předává přebytečná data dál. Poslední modul v řetězci už pouze přijímá data a dál je neposílá.
Lze si to představit jako řadu nádob, do kterých postupně naléváme vodu. Nejprve se zaplní první nádoba, přebytek přeteče do druhé, potom do třetí a tak dále. U MAX7219 místo vody putují data.
Právě proto má modul dva pětipinové konektory – jeden vstupní a druhý výstupní. Při zapojování je důležité nezaměnit jejich směr!
S rostoucím počtem modulů se samozřejmě prodlužuje i doba potřebná pro přenos dat. U několika matic to ale zpravidla nepředstavuje žádný praktický problém.
- Poznámka:
- Při větším počtu propojených modulů je třeba věnovat pozornost napájení. Každá LED matice odebírá určitý proud a několik zobrazovačů už může představovat poměrně významnou zátěž. To je důležité zejména tehdy, pokud bychom chtěli všechny moduly napájet pouze ze stabilizátoru 3,3 V na desce ESP32.
V dalších ukázkách budeme používat pouze dvojici modulů MAX7219. Obecný princip zapojení je ale stejný i pro delší řetězce. Příklad propojení čtyř modulů ukazuje obrázek č. 4.
Obrázek č. 4 – kaskádovité zapojení modulů MAX7219
zdroj obrázku: https://
Na obrázku je dobře vidět jedna z výhod originálních modulů – lze je propojit přímo přes konektory a vytvořit téměř souvislý zobrazovací řádek. Mechanicky to sice není úplně ideální řešení, ale pro většinu bastlířských projektů bohatě postačí.
Elektronické schéma modulu MAX7219
Abychom nezůstali jen u hotových modulů, podíváme se ještě na jejich elektronické schéma (obr. č. 5). Na první pohled se může zdát, že jde pouze o zajímavost pro milovníky elektroniky. Jak ale uvidíme v další části článku, právě schéma nám pomůže odpovědět na jednu důležitou otázku týkající se napájení a logických úrovní při propojení s modulem ESP32.
Obrázek č. 5 – elektronické schéma LED zobrazovače s MAX7219
zdroj obrázku: https://
Připojení modulu MAX7219 k modulu ESP32
Pozorný čtenář už si možná všiml, že modul MAX7219 komunikuje prostřednictvím sériové sběrnice SPI. Přenos dat je jednosměrný – mikrokontrolér pouze odesílá data do zobrazovače.
Pro komunikaci se používají tři signály:
DIN– datový vstup,CLK– hodinový signál,CS– výběr obvodu (Chip Select).
Z pohledu modulu ESP32 jsou všechny tři signály běžnými digitálními výstupy.
ESP32 obsahuje hardwarový řadič SPI, takže můžeme využít jeho výchozí komunikační piny. Druhou možností je použít softwarovou implementaci sběrnice SoftSPI, která umožňuje zvolit téměř libovolné GPIO piny. Právě tuto variantu budeme v dalších ukázkách používat, protože poskytuje větší volnost při návrhu zapojení.
Napájení modulu
Zbývající dva piny modulu slouží k napájení. Podle datasheetu vyžaduje obvod MAX7219 napájecí napětí v rozsahu 4,0 až 5,5 V. To ale otevírá nám již známou otázku:
„Jak bezpečně propojit pětivoltový modul s ESP32, které používá logické úrovně 3,3 V?“
V praxi se používají dvě možnosti.
1. Napájení modulu 5 V
Většina vývojových kitů ESP32 je napájena z USB, tedy napětím 5 V. Samotný mikrokontrolér ale pracuje s logickými úrovněmi 3,3 V, takže přivádět pětivoltové signály přímo na jeho vstupy není správné.
Rozhodující proto je, zda modul MAX7219 při napájení 5 V nepřenáší toto napětí také na komunikační piny DIN, CLK a CS.
Pohled do schématu (viz obr. 5) ukazuje, že tyto vstupy nejsou přes pull-up rezistory připojeny k napájení. Pro jistotu jsme provedli i praktické měření. Na všech třech pinech jsme naměřili přibližně 40 mV (obr. č. 6), tedy prakticky nulové napětí.
Z toho lze usuzovat, že vstupy obvodu MAX7219 lze bez problémů řídit i logikou 3,3 V, přestože datasheet tuto možnost výslovně nepopisuje.
Obrázek č. 6 – měření napětí na pinech DIN, CS a CLK při napájení modulu MAX7219 napětím 5 V
2. Napájení modulu 3,3 V
Na první pohled se může zdát, že napájení 3,3 V je elegantnější řešení. Odpadá totiž rozdíl logických úrovní mezi ESP32 a zobrazovačem.
Má to však jeden háček. Datasheet uvádí minimální napájecí napětí 4,0 V, takže provoz při 3,3 V je mimo garantované parametry výrobce.
Přesto mnoho bastlířů uvádí, že modul při napájení 3,3 V funguje bez problémů. Stejnou zkušenost máme i my. Ani při běžném používání jsme nepozorovali žádné problémy s komunikací nebo avizovaný výrazně nižší jas LED matice, o kterém se někdy diskutuje.
Nevýhodou této varianty je spíše použití napájení pomocí samotné desky ESP32. Napětí 3,3 V je totiž dodáváno vestavěným stabilizátorem, který kromě mikrokontroléru napájí i všechny připojené periferie. U jedné LED matice to zpravidla nepředstavuje problém, ale při větším počtu zřetězených modulů nebo současném využití Wi-Fi už bude stabilizátor zbytečně přetěžován.
Tak 3,3 V, nebo 5 V?
Obě varianty mají své výhody i nevýhody.
Napájení 3,3 V sjednocuje logické úrovně, ale pracuje mimo specifikaci výrobce a zatěžuje stabilizátor na desce ESP32.
Napájení 5 V naopak odpovídá doporučení datasheetu. Naše měření navíc ukázalo, že komunikační vstupy modulu nepředstavují pro ESP32 žádné riziko.
Proto budeme v dalším textu používat napájení 5 V, které považujeme za bezpečnější a obecně doporučené řešení.
Připojení modulu MAX7219 k modulu ESP32
Na obrázku č. 7 je znázorněno zapojení dvojice modulů MAX7219 k vývojovému kitu ESP32. Právě toto zapojení budeme používat ve všech následujících ukázkách programů v MicroPythonu.
Obrázek č. 7 – blokové schéma připojení dvojice modulů MAX7219 k vývojovému kitu modulu ESP32
Zapojení jednotlivých signálů shrnuje tabulka č. 1.
| Modul ESP32 | Modul MAX7219 |
|---|---|
| 5 V | VCC |
| GND | GND |
| GPIO33 MOSI (Master Out Slave In) |
DIN (Data In) |
| GPIO25 CS (Chip Select) |
CS (Chip Select) |
| GPIO26 SCK (Serial Clock) |
CLK (Clock) |
Tab. č. 1 – propojení LED modulu MAX7219 a vývojového kitu ESP32
Na schématu je kromě běžného propojení naznačena také možnost samostatného napájecího zdroje 5 V. Pokud bude k ESP32 připojeno více LED matic nebo bude současně využívána například Wi-Fi, je vhodnější napájet celý systém dostatečně dimenzovaným externím zdrojem.
Pro první pokusy si ale vystačíme s napájením z USB konektoru. Ale je jistě dobré mít na paměti, že i USB má své proudové limity.
- Poznámka:
- Na obrázku č. 7 jsou zobrazeny generické moduly MAX7219 s integrovaným obvodem v pouzdře DIL. Na rozdíl od originálních modulů není čip ukryt pod LED maticí, ale je umístěn vedle ní. To sice usnadňuje výrobu, ale při skládání více modulů vedle sebe vznikají mezi jednotlivými LED maticemi mezery. Pokud tedy plánujeme vytvořit souvislý informační LED řádek, je dobré s tím počítat už při výběru konkrétního typu modulu.
Řízení modulu MAX7219 v MicroPythonu pro ESP32
Pro ovládání LED zobrazovačů s řadičem MAX7219 je v prostředí MicroPython k dispozici několik různých knihoven. Ty se od sebe liší nejen nabízenými funkcemi a způsobem práce se zobrazovačem, ale někdy také doporučeným zapojením k modulu ESP32. V následující části si představíme některé knihovny a ukážeme si jejich možnosti.
Abychom při zkoušení jednotlivých knihoven nemuseli neustále měnit zapojení, budeme ve všech příkladech používat stejné připojení modulu MAX7219 k ESP32 (viz obrázek č. 7), jehož zapojení je uvedeno v tabulce č. 1. Díky tomu bude možné přecházet mezi jednotlivými ukázkami pouze změnou programu.
Při komunikaci využijeme rozhraní SoftSPI, tedy softwarově emulovanou sběrnici SPI. Ta umožňuje zvolit libovolné vhodné vývody modulu ESP32 pro signály SCK (Serial Clock), MOSI (Master Out Slave In) a CS (Chip Select), což poskytuje větší flexibilitu při návrhu zapojení. U každé použité knihovny bude proto potřeba pouze upravit příslušnou část programu, ve které se vytváří a nastavuje objekt pro komunikaci sběrnice SPI.
Pro všechny pokusy budou k modulu ESP32 připojeny dva LED zobrazovače 8×8 s řadičem MAX7219 zapojené do kaskády. Díky tomu bude dobře patrné, jak se jednotlivé funkce knihoven a jejich nastavení chovají při ovládání více zobrazovacích modulů současně, což odpovídá běžnému způsobu použití těchto zobrazovačů v praxi.
Knihovna max7219.py – autor: Vincent Rialland
Pro první pokusy využijeme knihovnu max7219.py, jejímž autorem je Vincent Rialland. Knihovna je volně dostupná na GitHubu (https://
Po nahrání knihovny do modulu ESP32 a zapojení podle tabulky č. 1 můžeme vyzkoušet první ukázkový program. Vychází z příkladu uvedeného autorem knihovny, pouze jsme jej upravili pro použití sběrnice SoftSPI a pinů podle našeho zapojení.
from machine import Pin, SoftSPI
from max7219 import Max7219 # zdroj knihovny: https://github.com/vrialland/micropython-max7219
max_clk = const(26)
max_cs = const(25)
max_din = const(33)
spi = SoftSPI(baudrate=500000, polarity=1, phase=0, sck=max_clk, mosi=max_din, miso=14)
screen = Max7219(8, 8, spi, Pin(max_cs))
screen.text('1', 0, 0, 1)
screen.show()
Program je velmi krátký a přehledný. Po inicializaci sběrnice SPI a definici použitých GPIO pinů nás budou zajímat především tři příkazy.
Prvním z nich je vytvoření objektu zobrazovače:
screen = Max7219(8, 8, spi, Pin(max_cs))
Obecný tvar konstruktoru je:
Max7219(width, height, spi, cs, rotate_180=False)
Parametry mají následující význam:
width– šířka zobrazovací plochy v pixelech,height– výška zobrazovací plochy v pixelech,spi– použité rozhraní SPI,cs– zvolený pinChip Select,rotate_180– otočení obrazu o 180° (FalseneboTrue).
V našem případě (zatím) vytváříme zobrazovací plochu o velikosti 8×8 pixelů, tedy přesně jednu LED matici.
Další příkaz
screen.text('1', 0, 0, 1)
vykreslí do pracovní paměti knihovny znak 1 na souřadnice [0, 0]. Poslední parametr určuje barvu vykreslování. U monochromatického zobrazovače znamená hodnota 1, že příslušné LED budou svítit.
Důležité je uvědomit si, že tímto příkazem se zatím na LED matici nic nezobrazí. Knihovna pouze upraví obsah svého framebufferu, tedy pracovní obrazové paměti.
Teprve příkaz
screen.show()
přenese celý obsah frame do zobrazovače. Díky tomu lze připravit celý obraz a zobrazit jej najednou bez viditelného překreslování.
Po spuštění programu v prostředí Thonny dostaneme výsledek znázorněný na obrázku č. 8.
Obrázek č. 8 – zobrazení číslice "1" při nastavení pracovní plochy 8×8 pixelů
Možná vás překvapí, že stejný výsledek dostaneme i při pokusu zobrazit řetězec "12". Důvod je jednoduchý. Objekt screen představuje zobrazovací plochu širokou pouze osm pixelů, takže knihovna považuje obě LED matice za jeden a tentýž zobrazovač.
Řešení spočívá ve vytvoření větší pracovní plochy:
screen = Max7219(16, 8, spi, Pin(max_cs))
Nyní knihovna pracuje s obrazem o velikosti 16×8 pixelů. První LED matice představuje souřadnice 0 až 7, druhá souřadnice 8 až 15. Delší text se proto může rozprostřít přes oba zobrazovače.
Obrázek č. 9 – zobrazení textu při pracovní ploše 16×8 pixelů
Možná jste si ale všimli ještě jedné zajímavosti. Číslice se zobrazily v opačném pořadí, než bychom intuitivně očekávali.
Důvod jsme si vysvětlili už při popisu kaskádového zapojení. Data vstupují do prvního modulu, ale každý nově přijatý bajt postupně posouvá předchozí obsah do dalšího obvodu v řetězci. Z pohledu zobrazovaného textu je proto první LED matice ta, která je od modulu ESP32 nejdále.
Na tuto vlastnost je dobré myslet už při návrhu zařízení. Po zabudování zobrazovačů do krabičky by jejich vzájemná výměna nebyla zrovna příjemnou záležitostí.
Vystačíme si jen se zobrazováním znaků?
Knihovna samozřejmě umí mnohem více než pouze vypisovat text. Dokumentace na GitHubu je ale poměrně stručná a většina pokročilejších funkcí v ní není popsána.
Praktickým pomocníkem může být některý z dnešních AI asistentů. Stačí mu předložit zdrojový kód knihovny a požádat jej o přehled uživatelsky dostupných metod. V případě MicroPythonu bývají výsledky překvapivě kvalitní a během několika okamžiků získáme přehled funkcí, které můžeme ve svých programech využít.
Zjistíme například, že třída Max7219 obsahuje nejen vlastní metody, ale zároveň dědí všechny grafické funkce třídy framebuf.. Máme tedy k dispozici nejen jednoduché zobrazování textu, ale i kreslení bodů, čar, obdélníků, výplní a dalších grafických objektů.
Co vlastně knihovna umí?
Zdrojový kód jsme nechali analyzovat pomocí AI asistenta a požádali jej o vytvoření přehledu všech uživatelsky dostupných metod. I takto si musí bastlíř pomoci, když oficiální dokumentace chybí.
Ukázalo se, že knihovna vlastně nabízí dvě skupiny funkcí.
- několik vlastních metod určených přímo pro obvod MAX7219,
- kompletní sadu grafických funkcí zděděných z třídy
framebuf.FrameBuffer.
To je velmi příjemné zjištění. Autor knihovny totiž nemusel znovu vytvářet všechny kreslicí funkce. Využil již existující třídu FrameBuffer, která je součástí MicroPythonu, a svou knihovnu rozšířil pouze o to, co souvisí s obvodem MAX7219.
Metody třídy Max7219
Samotná knihovna přidává pouze několik metod, které přímo souvisejí s obsluhou zobrazovače.
Konstruktor:
Max7219(width, height, spi, cs, rotate_180=False)
Vytvoří objekt zobrazovače. (Tuto funkci jsme již poznali)
Parametry:
width– šířka zobrazovací plochy v pixelech,height– výška zobrazovací plochy v pixelech,spi– použité rozhraní SPI,cs– pinChip Select,rotate_180– otočení obrazu o 180°.
brightness(value)- Nastaví jas LED matice v rozsahu 0 až 15.
display.brightness( 0) #minimální jas
display.brightness( 15) #maximální jas show()- Přenese obsah
framena LED zobrazovač. Dokud tuto metodu nezavoláme, všechny změny zůstávají pouze v pracovní paměti.bufferu init_display() - Znovu inicializuje obvod MAX7219. Za běžných okolností ji není třeba volat, protože se automaticky provede při vytvoření objektu.
Metody zděděné z FrameBuffer
Díky dědičnosti máme automaticky k dispozici všechny grafické funkce třídy FrameBuffer, takže můžeme kreslit body, čáry, obdélníky nebo vypisovat text stejně jako na jiných grafických displejích podporovaných MicroPythonem.
fill(color)- Vyplní celý
frame.buffer display.fill(0) # smazání displeje
display.fill(1) # rozsvítí všechny LEDpixel(x, y) - Vrátí stav zvoleného pixelu.
stav = display.pixel( 3, 5) pixel(x, y, color) - Nastaví stav jednoho pixelu.
display.pixel( 3, 5, 1)
display.pixel(3, 5, 0)hline(x, y, width, color) - Nakreslí vodorovnou čáru.
display.hline( 0, 4, 20, 1) vline(x, y, height, color) - Nakreslí svislou čáru.
display.vline( 5, 0, 8, 1) line(x1, y1, x2, y2, color) - Nakreslí úsečku mezi dvěma body.
display.line( 0, 0, 15, 7, 1) rect(x, y, width, height, color) - Nakreslí obdélník.
display.rect( 0, 0, 16, 8, 1) fill_rect(x, y, width, height, color) - Nakreslí vyplněný obdélník.
display.fill_rect( 2, 2, 5, 3, 1) text(text, x, y, color) - Vypíše text pomocí vestavěného fontu.
display.text( "Ahoj", 0, 0, 1) - Vestavěný font používá znakovou sadu 8bit ASCII a má výšku osm pixelů.
scroll(dx, dy) - Posune obsah
frame.bufferu display.scroll( -1, 0) blit(fbuf, x, y) - Zkopíruje obsah jiného
frame.bufferu display.blit( other_fb, 0, 0)
Najednou máme k dispozici poměrně slušnou grafickou knihovnu. Můžeme kreslit jednotlivé body, čáry, obdélníky, vytvářet jednoduché animace nebo posouvat celý obsah displeje. Přitom jsme se naučili pouze několik málo funkcí.
A teď si neodpustíme jednu poznámku.
Ať už si o umělé inteligenci myslíme cokoliv, právě tady odvádí výbornou práci. Místo pročítání několika stovek řádků zdrojového kódu stačí položit správnou otázku a během několika sekund máme hotový přehled celé knihovny.
A malý vzkaz autorům knihoven: Když už to AI zvládne vytvořit za pár sekund, možná by stálo za to přidat podobný přehled rovnou na GitHub. Ušetřilo by to spoustu času všem ostatním. 😉
Jednoduchá animace
Abychom nezůstali jen u seznamu funkcí, vyzkoušíme si krátký program, který využívá kreslení úseček. O samotné překreslování LED matice se stará knihovna, my pouze určujeme, co se má do frame nakreslit.
from machine import Pin, SoftSPI
from max7219 import Max7219 # zdroj knihovny: https://github.com/vrialland/micropython-max7219
from time import sleep
max_clk = const(26)
max_cs = const(25)
max_din = const(33)
spi = SoftSPI(baudrate=500000, polarity=1, phase=0, sck=max_clk, mosi=max_din, miso=14)
screen = Max7219(16, 8, spi, Pin(max_cs))
screen.brightness(1)
screen.fill(0)
screen.show()
pauza = 0.03
while True:
for i in range(8):
screen.line(0, i, 7, 7-i, 1)
screen.line(0, 7-i, 7, i, 1)
screen.show()
sleep(pauza)
screen.fill(0)
screen.show()
for i in range(6):
screen.line(6-i, 0, i+1, 7, 1)
screen.line(i+1, 0, 6-i, 7, 1)
screen.show()
sleep(pauza)
screen.fill(0)
screen.show()
Program nebudeme rozebírat řádek po řádku. Zkuste si jej spustit a chvíli pozorovat jeho chování. Na videu č. 1 je jeho chování zachyceno při nastavení pracovní plochy 16×8 pixelů.
Pokud v konstruktoru objektu screen změníte rozměr pracovní plochy na 8×8, bude animace probíhat současně na obou LED maticích. Po předchozích kapitolách už vás ale takové chování nejspíš nepřekvapí.
Knihovna max7219.py – autor Mike Causer
Další možností pro ovládání LED matic s obvodem MAX7219 je knihovna max7219.py od autora Mikea Causera – https://
V praxi se zde ukazuje nepříjemná realita – různí autoři často řeší stejný problém trochu jinak, ale své knihovny nijak jednoznačně neodlišují. V tomto případě dokonce sdílejí stejný název souboru i stejné názvy metod. Výsledkem je, že knihovny mohou být na první pohled snadno zaměnitelné, i když nejsou vzájemně kompatibilní.
Proto je při práci s projekty dobré vždy uchovávat knihovnu přímo u zdrojových kódů, případně si do programu připsat poznámku o původu použité knihovny, na kterou se program okazuje. Název max7219.py totiž sám o sobě neříká, o jakou implementaci jde.
Z hlediska funkcí, jak bylo naznačeno, jsou si obě knihovny velmi podobné. Obě využívají FrameBuffer, poskytují základní grafické operace a umožňují nastavení jasu i aktualizaci displeje. Pro programátora se tak rozdíl v kreslení bodů, čar nebo textu prakticky neprojeví.
Rozdíl nastává ale při inicializaci displeje. Zatímco první knihovna pracuje s celkovou šířkou a výškou zobrazovací plochy (například 16×8 nebo 32×8 pixelů) a bere celý řetězec modulů jako jednu souvislou plochu, tato knihovna vychází z počtu připojených modulů. Každý z nich má pevnou velikost 8×8 bodů a výsledná plocha vzniká jejich automatickým propojením.
Možná by se mohlo zdát, že tento rozdíl zásadně ovlivní způsob programování. Ale pak se ukáže, že je to velmi podobné. Programátor vždy pracuje s jednou společnou souřadnicovou soustavou a nemusí řešit, na kterém konkrétním modulu se data zobrazí. Knihovna sama podle počtu modulů rozloží obsah frame do kaskády. (Aspoň že tak!)
Například při zobrazení textu "12" stačí zavolat: text("12", 0, 0) a následně show(). Pokud se text nevejde na jeden modul, automaticky se zobrazí přes více matic. Není tedy nutné řešit, že každá číslice patří na jiný modul.
Stejný princip platí i pro grafiku. Při dvou modulech vedle sebe vzniká souřadnicová plocha 16×8 bodů, kde souřadnice x nabývá hodnot 0 až 15. První modul pokrývá oblast 0–7, druhý 8–15. Pokud tedy chceme kreslit jen na druhý modul, jednoduše použijeme odpovídající rozsah souřadnic – knihovna se o správné rozdělení dat postará sama při volání show().
Další rozdíl je v názvu hlavní třídy. U první knihovny používáme:
from max7219 import Max7219
zatímco zde:
from max7219 import Matrix8x8
Je tedy nutné nejen upravit parametry konstruktoru, ale i samotný název třídy.
U první knihovny může inicializace vypadat například takto:
screen = Max7219(32, 8, spi, cs)
u této knihovny musíme použít:
screen = Matrix8x8(spi, cs, 2)
kde poslední parametr určuje počet zřetězených modulů.
Tyto dva rozdíly – tedy název třídy a způsob inicializace – jsou klíčové při přechodu mezi knihovnami.
Úvodní příklad zobrazení čísla "12" na dvou modulech pak vypadá takto:
from machine import Pin, SoftSPI
from max7219 import Matrix8x8 # zdroj knihovny: https://github.com/mcauser/micropython-max7219
max_clk = const(26)
max_cs = const(25)
max_din = const(33)
spi = SoftSPI(baudrate=500000, polarity=1, phase=0, sck=max_clk, mosi=max_din, miso=14)
screen = Matrix8x8(spi, Pin(max_cs), 2) #sbernice, pin CS, počet zobrazovačů
screen.text('12',0,0,1)
screen.show()
Výsledek je shodný s tím, co jsme viděli dříve na obrázku č. 9. Pokud necháme připojené dva LED zobrazovače, ale v inicializaci nastavíme jen jeden zobrazovač, budou se oba zobrazovače chovat stejně jako u předchozí knihovny, když jsme nastavili souřadnicový prostor na 8x8 – tedy budou oba zobrazovat totéž, takže číslici 1 (stejně jako na obrázku č. 8).
Mohli bychom nadále procházet internet a postupně tu prezentovat knihovny dalších a dalších autorů. Postupně bychom zjišťovali, že ve své podstatě jde o podobné, leč vzájemně nekompatibilní kódy. Občas některý z autorů přidá nějakou tu užitečnou funkci. Místo toho se dále podíváme na trochu jiný problém, na který narazíme, pokud použijeme generické LED zobrazovače.
Problém generického zobrazovače
Pokud používáme originální LED moduly s MAX7219, kde je řadič skrytý přímo pod LED maticí, dají se jednotlivé kusy poměrně snadno spojovat do delších řetězců – jak jsme viděli na obrázku č. 4.
U generických modulů ale narazíme na mechanický problém. Pokud chceme vytvořit souvislý LED pruh a moduly umístit těsně vedle sebe, překáží část desky plošného spoje s obvodem MAX7219. Moduly tak nelze jednoduše „srovnat na doraz“.
Řešením je otočit jednotlivé moduly o 90° a skládat je bokem, aby vznikla souvislá zobrazovací plocha (viz obrázek č. 10). Tím ale vzniká nový problém – zobrazovaný motiv je nutné také otočit o 90°.
Ale tuto situaci zatím žádná z použitých knihoven přímo nepodporuje!
Je pravda, že první knihovna od Vincenta Riallanda nabízí volbu otočení o 180°, to však pro tento případ nestačí – potřebujeme pracovat právě s rotací o 90°, případně 270°.
Obrázek č. 10 – uspořádání generických zobrazovačů pro vytvoření textového řádku
V tu chvíli nezbývá než sáhnout přímo do zdrojového kódu jedné z knihoven a upravit její chování.
Pro úpravu jsme zvolili knihovnu od autora Mikea Causera. Důvod není nijak zásadní – jednoduše jsme si jednu z dostupných implementací museli vybrat. 😊
Úprava knihovny
Otevřeme si zdrojový kód zvolené knihovny v editoru Thonny. Naštěstí nejde o žádný rozsáhlý projekt, takže se v něm dá rychle zorientovat.
První úprava se týká inicializace objektu, který řídí LED zobrazovač. Jak už víme z předchozích dílů našeho seriálu, jedná se o metodu __init__ třídy Matrix8x8.
Její původní definice vypadá takto:
class Matrix8x8:
def __init__(self, spi, cs, num):
atd.
Do této definice přidáme nový parametr pro natočení obrazu. Aby zůstala zachovaná zpětná kompatibilita, nastavíme jej jako volitelný s výchozí hodnotou.
Zároveň je vhodné doplnit konstanty, které budou jednotlivé režimy rotace popisovat čitelným způsobem. Můžeme tak rozšířit třídu Matrix8x8 i o užitečné konstanty – například následovně:
class Matrix8x8:
# Konstanty pro rotaci
ROTATE_0 = const(0)
ROTATE_90 = const(1)
ROTATE_180 = const(2)
ROTATE_270 = const(3)
def __init__(self, spi, cs, num, rotate=ROTATE_0):
atd.
Tím vzniká možnost jednoduše volit orientaci zobrazení přímo při vytváření objektu:
display = Matrix8x8(spi, Pin(max_cs), 2, rotate=Matrix8x8.ROTATE_270)
Máme tím hotovo? Ne! Ve skutečnosti zatím nemáme žádnou funkční změnu – přidali jsme jen nový parametr a pojmenované konstanty.
Aby se rotace skutečně projevila, je potřeba uložit hodnotu do objektu. To se v Pythonu provádí přes self:
self.rotate = rotate
Úvodní část inicializace pak může vypadat takto:
class Matrix8x8:
# Konstanty pro rotaci
ROTATE_0 = const(0)
ROTATE_90 = const(1)
ROTATE_180 = const(2)
ROTATE_270 = const(3)
def __init__(self, spi, cs, num, rotate=ROTATE_0):
self.spi = spi
self.cs = cs
self.num = num
self.rotate = rotate
atd.
Tím máme v objektu uloženou informaci o orientaci zobrazení, se kterou můžeme dále pracovat.
Dalším krokem je samotné odesílání dat do MAX7219. Právě tam se rozhodne, jak bude obraz skutečně vykreslen. Klíčová je metoda show, kterou si upravíme v další části.
Metoda show
V původní verzi knihovny vypadá metoda show následovně:
def show(self):
for y in range(8):
self.cs(0)
for m in range(self.num):
self.spi.write(bytearray([_DIGIT0 + y, self.buffer[(y * self.num) + m]]))
self.cs(1)
Na první pohled jde o dvojici vnořených cyklů. Vnější cyklus postupně prochází jednotlivé řádky (0–7), vnitřní pak jednotlivé moduly v kaskádě. Data pro zobrazení jsou uložená v poli buffer. Metoda je po řádcích posílá do obvodu MAX7219.
Klíčová myšlenka celé naší úpravy je jednoduchá: Nemusíme měnit samotná data v bufferu, ale můžeme je upravit až při odesílání. Tím získáme možnost obraz „přetočit“, aniž bychom zasahovali do frame.
Úprava pro rotaci
Upravená verze metody show může vypadat například takto (pro případ rotace o 90°):
def show(self):
"""Zobrazí obsah bufferu na LED maticích, s případnou rotací"""
for y in range(8):
self.cs(0)
for m in range(self.num):
index = (y * self.num) + m
byte = self.buffer[index]
if self.rotate == ROTATE_0: # žádná rotace
pass
elif self.rotate == self.ROTATE_90: # Otočení o 90°
byte = 0
for bit in range(8):
src_idx = (bit * self.num) + (self.num - 1 - m)
if self.buffer[src_idx] & (1 << (7 - y)):
byte |= (1 << bit)
# sem je mozne vlozit dalsi otaceni
self.spi.write(bytearray([_DIGIT0 + y, byte]))
self.cs(1)
Do původní struktury jsme doplnili podmínky, které rozhodují, zda se data odešlou beze změny, nebo transformovaná.
ROTATE_0 zachovává původní chování knihovny.
ROTATE_90 provádí přepočet bitů tak, aby se obraz otočil.
Obdobně můžeme vložit podmínky i pro ROTATE_180 a ROTATE_270. Pak budeme moci nastavovat výchozí orientaci LED zobrazovačů téměř libovolně.
elif self.rotate == ROTATE_180: # Otočení o 180°
rot_y = 7 - y
rot_m = self.num - 1 - m
index_rot = (rot_y * self.num) + rot_m
byte = self.buffer[index_rot]
byte = ((byte & 0x01) << 7) | ((byte & 0x02) << 5) | ((byte & 0x04) << 3) | ((byte & 0x08) << 1) | \
((byte & 0x10) >> 1) | ((byte & 0x20) >> 3) | ((byte & 0x40) >> 5) | ((byte & 0x80) >> 7)
elif self.rotate == self.ROTATE_270: # Otočení o 270°
byte = 0
for bit in range(8):
src_idx = ((7 - bit) * self.num) + m
if self.buffer[src_idx] & (1 << y):
byte |= (1 << bit)
Důležité je, že se nemění obsah buffer, ale pouze výsledná data (byte), která se odesílají do MAX7219. Tím zůstává frame konzistentní a nezávislý na způsobu zobrazení.
Logika rotace v podstatě přemapovává souřadnice pixelů – z původní matice se stává její otočená varianta. U více modulů (self.num) se tento princip opakuje pro každý čip v řetězci.
- POZNÁMKA k složitosti
- Samotný výpočet indexů může na první pohled působit komplikovaně. Ve skutečnosti jde ale jen o přemapování řádků a sloupců mezi původní a otočenou maticí.
- Právě v takových situacích se ukazuje výhoda práce s
frame– umožňuje oddělit „co kreslím“ od toho „jak se to fyzicky zobrazí“.bufferem
Obrázek č. 11 – zobrazení otočených (o 270°) výstupů na generických LED zobrazovačích
- Malá POZNÁMKA k AI
- Podobné úpravy knihoven dnes často vznikají i s pomocí nástrojů umělé inteligence. Ne jako náhrada porozumění kódu, ale jako rychlá pomůcka při návrhu algoritmů nebo přemapování dat.
- Důležité ale zůstává, že i když AI pomůže s návrhem, je potřeba rozumět tomu, co výsledný kód skutečně dělá – zvlášť pokud jde o knihovnu, na které stojí další projekty.
Další možná rozšíření:
Když už jsme se „povrtali“ v knihovně MAX7219 a pochopili, jak se data ukládají a odesílají na výstup, otevírá se prostor pro další vylepšení. V tu chvíli se přímo nabízí myšlenka vytvořit si vlastní rozšířenou verzi – takovou malou „superknihovnu“, která si vezme inspiraci z různých implementací dostupných na internetu.
Následující ukázky proto představují několik užitečných metod, které se mohou do knihovny hodit. U každé je uveden i zdroj inspirace.
Selektivní smazání LED matice
Pokud knihovna pracuje s více 8×8 moduly jako s jednou paměťovou plochou, můžeme využít její rozdělení na jednotlivé segmenty a mazat jen konkrétní matici.
# inspirovano od: https://newbiely.com/tutorials/arduino-micropython/arduino-micropython-led-matrix
def clear(self, MatrixNo=1):
"""
Vymaže obsah zvolené LED matice (tj. 8x8 oblast).
MatrixNo: Číslo matice (1 = první zprava), výchozí je 1.
"""
if not (1 <= MatrixNo <= self.num):
raise ValueError("MatrixNo mimo rozsah")
x_offset = (MatrixNo - 1) * 8
self.framebuf.fill_rect(x_offset, 0, 8, 8, 0)
self.show()
Mazání je zde řešeno jednoduše – vykreslením prázdného (černého) obdélníku přes vybranou část displeje.
Skrolování textu
Další často používaná funkce je posuv textu přes více modulů. Kdo by nechtěl LED řádek, po kterém běží text z jedné stany na druhou.
# inspirovano od: http://multiwingspan.co.uk/pico.php?page=max7219
def scroll(self, text, delay=10, distance=None, prefix=' '):
"""
Posouvá text zprava doleva po maticích.
delay: zpoždění v ms mezi kroky
distance: délka posunu (výchozí: délka textu * 8 pixelů)
prefix: mezery před textem
"""
text = prefix + text
if not distance:
distance = len(text) * 8
for i in range(distance):
self.text(text, -i)
sleep_ms(delay)
Text se posouvá postupným posouváním souřadnice, takže vzniká jednoduchý „běžící“ efekt přes celou kaskádu LED matic (viz následující video č. 2).
Vykreslení vzoru (pattern)
Pro práci s vlastními symboly se hodí možnost vykreslit bitmapu definovanou jako pole znaků.
HEART = [
"01100110",
"11111111",
"11111111",
"11111111",
"01111110",
"00111100",
"00011000",
"00000000"
]
Metoda pattern pak tento vzor převede na LED matici:
# inspirovano od: https://github.com/srgalope/micropython-max7219v2024
def pattern(self, pattern, MatrixNo=1):
"""
Zobrazí vzor (pattern) na zvolené LED matici.
pattern: 2D seznam s '0' a '1' znaky (např. 8x8).
MatrixNo: Číslo matice (1 = první zprava), výchozí je 1.
"""
if not (1 <= MatrixNo <= self.num):
raise ValueError("MatrixNo mimo rozsah")
self.framebuf.fill(0)
x_offset = (MatrixNo - 1) * 8 # Vlevo posun o matici
for row_index, row in enumerate(pattern):
for col_index, pixel in enumerate(row):
if pixel == '1':
self.framebuf.pixel(x_offset + col_index, row_index, 1)
self.show()
Tímto způsobem lze snadno definovat vlastní symboly, jednoduché ikony nebo i malé animace – například „tepající“ srdce nebo veselý a smutný smajlík.
Možností, jak knihovnu rozšiřovat, je celá řada. Cílem ale není vytvořit univerzální „mega knihovnu“, která zvládne vše, a my z ní budeme využívat maximálně 5 %. Spíše dává větší smysl držet knihovnu jednoduchou a specifické funkce doplňovat přímo podle konkrétního projektu.
Následující ukázkový program by nám měl tuto filozofii trochu ukázat.
Rotující srdce:
K modulu ESP32 připojíme LED matici s řadičem MAX7219 a vytvoříme na ní jednoduchou animaci otáčejícího se srdce. Pro tento program použijeme knihovnu autora Mikea Causera, protože jsme si před chvílí ukázali, jak do ní přidat metodu pattern(), která dokáže vykreslit bitmapu definovanou jako pole řetězců. Tato metoda je poměrně užitečná a lze ji využít i v dalších projektech, takže má smysl ji do knihovny trvale doplnit.
Vytvoříme si tedy vlastní upravenou verzi knihovny.
Abychom se nedopouštěli stejného faux pas, které jsme před chvílí kritizovali u jiných autorů, uložíme ji pod novým názvem, například MAX7219_MC2.py.
Kód této knihovny bude následující:
"""
MicroPython max7219 cascadable 8x8 LED matrix driver
https://github.com/mcauser/micropython-max7219
MIT License
Copyright (c) 2017 Mike Causer
Pro článek na webu ESP32 DIYerLAB přidána metoda pattern (c)2026
"""
from micropython import const
import framebuf
_NOOP = const(0)
_DIGIT0 = const(1)
_DECODEMODE = const(9)
_INTENSITY = const(10)
_SCANLIMIT = const(11)
_SHUTDOWN = const(12)
_DISPLAYTEST = const(15)
class Matrix8x8:
def __init__(self, spi, cs, num):
"""
Driver for cascading MAX7219 8x8 LED matrices.
>>> import max7219
>>> from machine import Pin, SPI
>>> spi = SPI(1)
>>> display = max7219.Matrix8x8(spi, Pin(25), 4)
>>> display.text('1234',0,0,1)
>>> display.show()
"""
self.spi = spi
self.cs = cs
self.cs.init(cs.OUT, True)
self.buffer = bytearray(8 * num)
self.num = num
fb = framebuf.FrameBuffer(self.buffer, 8 * num, 8, framebuf.MONO_HLSB)
self.framebuf = fb
# Provide methods for accessing FrameBuffer graphics primitives. This is a workround
# because inheritance from a native class is currently unsupported.
# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html
self.fill = fb.fill # (col)
self.pixel = fb.pixel # (x, y[, c])
self.hline = fb.hline # (x, y, w, col)
self.vline = fb.vline # (x, y, h, col)
self.line = fb.line # (x1, y1, x2, y2, col)
self.rect = fb.rect # (x, y, w, h, col)
self.fill_rect = fb.fill_rect # (x, y, w, h, col)
self.text = fb.text # (string, x, y, col=1)
self.scroll = fb.scroll # (dx, dy)
self.blit = fb.blit # (fbuf, x, y[, key])
self.init()
def _write(self, command, data):
self.cs(0)
for m in range(self.num):
self.spi.write(bytearray([command, data]))
self.cs(1)
def init(self):
for command, data in (
(_SHUTDOWN, 0),
(_DISPLAYTEST, 0),
(_SCANLIMIT, 7),
(_DECODEMODE, 0),
(_SHUTDOWN, 1),
):
self._write(command, data)
def brightness(self, value):
if not 0 <= value <= 15:
raise ValueError("Brightness out of range")
self._write(_INTENSITY, value)
def show(self):
for y in range(8):
self.cs(0)
for m in range(self.num):
self.spi.write(bytearray([_DIGIT0 + y, self.buffer[(y * self.num) + m]]))
self.cs(1)
def pattern(self, pattern, MatrixNo=1): # inspirovano od: https://github.com/srgalope/micropython-max7219v2024
"""
Zobrazí vzor (pattern) na zvolené LED matici.
pattern: 2D seznam s '0' a '1' znaky (např. 8x8).
MatrixNo: Číslo matice (1 = první zprava), výchozí je 1.
"""
if not (1 <= MatrixNo <= self.num):
raise ValueError("MatrixNo mimo rozsah")
self.framebuf.fill(0)
x_offset = (MatrixNo - 1) * 8 # Vlevo posun o matici
for row_index, row in enumerate(pattern):
for col_index, pixel in enumerate(row):
if pixel == '1':
self.framebuf.pixel(x_offset + col_index, row_index, 1)
self.show()
Skutečně jsme pouze na konec původní knihovny doplnili výše uvedenou metodu pattern(). Takto upravenou knihovnu uložíme do zařízení s firmwarem MicroPython, tedy do našeho modulu ESP32.
Nyní máme dvě možnosti, jak vytvořit animaci otáčejícího se srdce.
- První možností je připravit si všechny čtyři otočené varianty srdce jako samostatné bitmapy a ty pak postupně zobrazovat. Je to nejjednodušší, nejrychlejší a z praktického hlediska často i nejefektivnější řešení.
- Druhý způsob bude o něco zajímavější. Vytvoříme si tři pomocné funkce, které budou bitmapu otáčet o 90°, 180° a 270°. Místo ukládání několika verzí stejného obrázku tedy budeme nové varianty generovat za běhu programu.
POZOR! zde se nyní nejedná o celou změnu orientace vykreslování na zobrazovač, jako jsme řešili výše při použití generických zobrazovačů, ale zde jde o transformaci konkrétní bitmapové matice, která má být vykreslena metodoupattern.
Protože se nejedná o funkce, které by byly obecně potřeba při práci s LED maticemi, nemá příliš smysl přidávat je přímo do knihovny. Programátor, který by zvolil první přístup s předdefinovanými bitmapami, by je stejně nikdy nevyužil. Rotace bitmapy je v tomto případě natolik specifická požadavku konkrétní aplikace, že je vhodnější ponechat ji přímo v programu.
Podívejme se nejprve na funkci pro otočení o 90°. Nazveme ji rotate_90_cw(). Jejím vstupním parametrem bude bitmapa uložená jako pole řetězců (matrix) a výstupem bude stejná bitmapa otočená o 90° ve směru hodinových ručiček.
def rotate_90_cw(matrix):
rotated = [] # sem budeme ukládat nové řádky (sloupce původní matice)
for c in range(8): # pro každý sloupec v původní matici
new_row = "" # začínáme nový řádek v otočené matici
for r in range(8): # projdeme každý řádek
original_char = matrix[7 - r][c] # vezmeme znak ze spodního řádku nahoru
new_row += original_char # přidáme znak do nového řádku
rotated.append(new_row) # přidáme nový řádek do výsledné matice
return rotated
Když se na tuto funkci podíváme, její princip je poměrně snadno pochopitelný. Opět zde máme dva vnořené cykly. Z původní matice se postupně čtou jednotlivé znaky a skládají se do nové matice.
Nejprve se procházejí jednotlivé sloupce původního obrázku. Z každého sloupce se následně vytvoří nový řádek výsledné bitmapy. Právě způsob výběru znaků z původní matice určuje, zda dojde k otočení o 90°, případně o 180° nebo 270°.
💡 To by nebyl Python!
V našem seriálu jsme si nechtěli ukazovat pouze práci s modulem ESP32 nebo používání různých knihoven. Cílem bylo také alespoň trochu poznat samotný jazyk Python, respektive MicroPython.
Jednou z jeho zajímavých vlastností je schopnost vyjádřit poměrně složité operace velmi stručným zápisem. Díky různým jazykovým konstrukcím lze stejnou funkcionalitu zapsat výrazně kratším kódem, aniž by se změnil výsledek programu.
Nevěříte?
Podívejte se na výše uvedenou funkci rotate_90_cw() a následně na její zkrácenou variantu:
def rotate_90_cw(matrix):
return [''.join(matrix[7 - r][c] for r in range(8)) for c in range(8)]
Ano, to je opravdu všechno!
Tyto dva řádky dělají přesně totéž jako předchozí víceřádková funkce.
Nevěříte? Tak vítejte v Pythonu!
Dobře, zkusme si ukázat, co se zde vlastně děje.
Na první pohled vypadá tento zápis méně přehledně. Ve skutečnosti však pouze nahrazuje oba cykly a několik pomocných proměnných jediným výrazem.
Vnější část:
[ ... for c in range(8)]
odpovídá původnímu cyklu:
for c in range(8):
a vytváří jednotlivé řádky výsledné matice.
Pro každý sloupec c se následně vykoná výraz:
''.join(matrix[7 - r][c] for r in range(8))
Ten nahrazuje vnitřní cyklus:
new_row = ""
for r in range(8):
new_row += matrix[7 - r][c]
Tzv. generátorový výraz (viz dále)
(matrix[7 - r][c] for r in range(8))
postupně vrací stejné znaky, které byly v delší variantě ukládány do proměnné original_char. Metoda join() je následně spojí do jednoho řetězce, čímž vznikne nový řádek otočené matice.
Celý zápis lze tedy číst zhruba takto:
Pro každý sloupec původní matice vytvoř nový řádek složený ze znaků odečítaných od spodního řádku směrem nahoru a všechny vzniklé řádky vrať jako seznam.
Výhodou tohoto zápisu je menší množství kódu a absence pomocných proměnných rotated, new_row a original_char. Funkce přitom provádí úplně stejnou operaci jako její delší varianta.
List comprehension
Pojďme si vysvětlit dvě konstrukce, které se v předchozím zápisu objevily. První z nich je takzvaný list comprehension, tedy zkrácený způsob vytváření seznamů.
Nahrazuje situace, kdy bychom nejprve vytvořili prázdný seznam a následně do něj v cyklu přidávali nové položky pomocí metody append().
Dlouhá varianta:
numbers = []
for i in range(5):
numbers.append(i * 2)
Zkrácená varianta:
numbers = [i * 2 for i in range(5)]
Oba zápisy vytvoří seznam:
[0, 2, 4, 6, 8]
Obecný tvar je: [výraz for prvek in kolekce]
Generátorový výraz
Generátorový výraz vypadá podobně jako list comprehension, ale používá kulaté závorky:
(i * 2 for i in range(5))
Na rozdíl od list comprehension nevytváří celý seznam najednou, ale jednotlivé hodnoty generuje až ve chvíli, kdy jsou potřeba.
Například:
g = (i * 2 for i in range(5))
Proměnná g není seznam. Je to generátor. Hodnoty se vytvářejí postupně při průchodu:
for x in g:
print(x)
Cyklus se nejprve zeptá generátoru: „Dej mi první hodnotu.“ Generátor vrátí 0. Poté následuje další dotaz: „Dej mi další hodnotu.“ Generátor vrátí 2. Proces pokračuje, dokud nejsou vráceny všechny hodnoty.
Výstup tedy bude:
0
2
4
6
8
Výhodou generátorů je nižší spotřeba paměti, protože není potřeba vytvářet celý seznam najednou.
V naší funkci generátorový výraz:
(matrix[7 - r][c] for r in range(8))
postupně generuje prvky:
matrix[7][c]
matrix[6][c]
matrix[5][c]
...
matrix[0][c]
Metoda join() je následně odebírá a skládá do jednoho řetězce:
''.join(matrix[7 - r][c] for r in range(8))
Abychom mohli dokončit animaci srdce, doplníme ještě funkce pro otočení o 180° a 270°. A když už jsme v tom, přidáme si i dvě bonusové funkce pro horizontální a vertikální zrcadlení bitmapy:
def rotate_90_cw(matrix):
return [''.join(matrix[7 - r][c] for r in range(8)) for c in range(8)]
def rotate_180(matrix):
return [''.join(matrix[7 - r][7 - c] for c in range(8)) for r in range(8)]
def rotate_270_cw(matrix):
return [''.join(matrix[r][7 - c] for r in range(8)) for c in range(8)]
# a neco malo od cesty
def flip_vertical(matrix):
return matrix[::-1]
def flip_horizontal(matrix):
return [row[::-1] for row in matrix]
Pro zkušenějšího programátora v Pythonu jde o elegantní ukázku síly jazyka. V jediném řádku se zde kombinuje průchod maticí, generátorový výraz i skládání řetězců. Python se v takových chvílích téměř povyšuje na úroveň magie – několik málo znaků dokáže provést překvapivě složitou operaci.
Jenže pro začátečníka nebo člověka, který se chce Python teprve učit, může být podobný zápis spíše děsivý než inspirující. Není vůbec ostuda podívat se na takový kód a říct si: „Netuším, co se tu právě stalo.“
Ve skutečnosti většina programátorů během své kariéry narazí na kusy kódu, kterým hned nerozumí.
Dobrá zpráva je, že MicroPython ani Python obecně nejsou o psaní podobných rébusů. Naprostou většinu úloh lze řešit přehledně, krok za krokem a srozumitelně i pro člověka, který programuje teprve několik týdnů. Krátký a „chytrý“ zápis bývá většinou důsledkem zkušeností autora, nikoli požadavkem samotného jazyka.
A pokud byste někdy na podobnou pythonovskou šílenost narazili, nemusíte propadat panice. Dnes už máme k dispozici AI nástroje, které dokážou během několika sekund vysvětlit význam jednotlivých částí kódu nebo jej přepsat do klasické, mnohem čitelnější podoby. To, co dříve vyžadovalo dlouhé studium dokumentace nebo pomoc zkušenějšího kolegy, lze dnes získat jednou dobře položenou otázkou.
Takže až na vás někdy vyskočí kus kódu připomínající spíše zaklínadlo než rozumný kód, berte to spíše jako zajímavou hádanku než jako překážku. S trochou zvědavosti – a případně s pomocí AI – se z každého podobného rébusu nakonec stane obyčejný a pochopitelný Python.
Následující výpis programu obsahuje všechny výše popsané funkce sice ve zkrácené ryze pythonovské podobě, ale zároveň ve víceřádkovém komentáři najdete jejich víceřádkové a snadněji pochopitelné podoby. U každé z nich je tak pochopitelné, jak se výsledná transformovaná bitmapa vytváří.
from machine import Pin, SoftSPI
from max7219_MC2 import Matrix8x8
from time import sleep
max_clk = 26
max_cs = 25
max_din = 33
spi = SoftSPI(baudrate=500000, polarity=1, phase=0, sck=max_clk, mosi=max_din, miso=14)
display = Matrix8x8(spi, Pin(max_cs), 1) # sbernice, pin CS, pocet zobrazpvacu, orientace
display.brightness(1)
HEART = [
"01100110",
"11111111",
"11111111",
"11111111",
"01111110",
"00111100",
"00011000",
"00000000",
]
def rotate_90_cw(matrix):
"""
Tato funkce otočí 8×8 matici o 90 stupňů po směru hodinových ručiček (CW = clockwise).
def rotate_90_cw(matrix):
rotated = [] # sem budeme ukládat nové řádky (sloupce původní matice)
for c in range(8): # pro každý sloupec v původní matici
new_row = "" # začínáme nový řádek v otočené matici
for r in range(8): # projdeme každý řádek
original_char = matrix[7 - r][c] # vezmeme znak ze spodního řádku nahoru
new_row += original_char # přidáme znak do nového řádku
rotated.append(new_row) # přidáme nový řádek do výsledné matice
return rotated
A teď pythonovsky:
Vnější list comprehension:
[ ... for c in range(8)] # Pro každý c od 0 do 7 (tj. pro každý sloupec původní matice) vytvoříme jeden nový řádek výsledné matice.
Pro pevný c postupně bereme znaky z řádků 7 - r při r od 0 do 7 — tedy od spodního řádku (index 7) nahoru (index 0), vždy ze sloupce c.
(matrix[7 - r][c] for r in range(8))
''.join(...) spojí všechny tyto znaky do jednoho řetězce.
"""
return [''.join(matrix[7 - r][c] for r in range(8)) for c in range(8)]
def rotate_180(matrix):
"""
Tato funkce otočí 8×8 matici o 180 stupňů po směru hodinových ručiček (CW = clockwise).
def rotate_180(matrix):
rotated = [] # výsledek: nová matice po otočení
for r in range(8): # pro každý řádek výsledné matice
new_row = "" # nový řádek, který budeme postupně plnit
for c in range(8): # pro každý sloupec v tomto řádku
# Spočítáme odkud vezmeme znak z původní matice
original_char = matrix[7 - r][7 - c] # vezmeme znak „zezdola zprava“
new_row += original_char # přidáme ho do nového řádku
rotated.append(new_row) # přidáme nový řádek do výsledné matice
return rotated
"""
return [''.join(matrix[7 - r][7 - c] for c in range(8)) for r in range(8)]
def rotate_270_cw(matrix):
"""
def rotate_270_cw(matrix):
rotated = [] # výsledná otočená matice
for c in range(8): # pro každý sloupec původní matice
new_row = "" # nový řádek v otočené matici
for r in range(8): # pro každý řádek
# vezmeme znak ze správné pozice při rotaci
original_char = matrix[r][7 - c]
new_row += original_char
rotated.append(new_row) # přidáme nový řádek do výsledku
return rotated
"""
return [''.join(matrix[r][7 - c] for r in range(8)) for c in range(8)]
display.fill(0)
display.show()
while True:
display.pattern(HEART, 1)
sleep(0.2)
display.pattern(rotate_90_cw(HEART), 1)
sleep(0.2)
display.pattern(rotate_180(HEART), 1)
sleep(0.2)
display.pattern(rotate_270_cw(HEART), 1)
sleep(0.2)
Na videu č. 3 pak vidíme výslednou animaci srdce během běhu programu.
KONEC aneb Teď začíná ta skutečná zábava…
Pokud jste naším seriálem o MicroPythonu na modulu ESP32 došli až sem, je třeba říci, že jsme společně ušli opravdu dlouhou cestu. Od okamžiku, kdy jsme do modulu ESP32 nahráli první firmware s MicroPythonem a postupně se naučili rozblikat LED diodu. Jak jsme pokračovali ovládáním GPIO pinů až po práci se sběrnicemi a různými čidly, přerušením, režimy spánku, tvorbou vlastních knihoven, nebo komunikací přes Wi-Fi. Poznali jsme společně základní stavební kameny, které nám nyní umožní vytvářet vlastní elektronické projekty – od těch jednoduchých až po překvapivě propracovaná zařízení.
Je pravda, že asi dnešním článkem celý seriál končí. Ve skutečnosti je tomu pro Vás přesně naopak. Končí totiž pouze část, ve které jste dostávali hotové informace. Teď přichází ta zajímavější fáze – začít je používat, kombinovat a rozvíjet podle vlastních nápadů.
Právě v tom totiž spočívá skutečné kouzlo bastlení. Nejde o to vzít cizí projekt, bezmyšlenkovitě ho zkopírovat a mít radost, že funguje. Jde o to říct si: „To je zajímavé, ale co kdybych to udělal trochu jinak?“ nebo „Dokázal bych do toho přidat další funkci?“ A najednou člověk zjišťuje, že potřebuje pochopit něco nového. Jednou je to komunikace po sériové lince, jindy práce se souborovým systémem, matematika, síťové protokoly nebo třeba základy návrhu plošných spojů. Věci, které mu možná ve škole připadaly nudné, zbytečné nebo příliš teoretické, najednou dostávají smysl, protože mu pomáhají posunout vlastní nápad o kus dál.
Dnešní doba navíc přináší nástroj, který mohou bastlíři využívat podobně jako dříve knihy, časopisy nebo internetová diskusní fóra – umělou inteligenci. Byla by ale škoda používat ji pouze jen jako generátor hotových řešení. Mnohem cennější je využívat ji jako trpělivého pomocníka, který dokáže vysvětlit neznámé pojmy, rozebrat zdrojový kód řádek po řádku, navrhnout alternativní řešení nebo ukázat, proč něco nefunguje. Největší přínos totiž nepřichází ve chvíli, kdy za vás AI problém vyřeší, ale tehdy, když vám pomůže pochopit, jak jej vyřešit vlastními silami.
Až tedy někdy narazíte na kus kódu, který bude připomínat spíš text staroegyptského papyru než program, nebo na datasheet plný neznámých zkratek a pojmů, nenechte se odradit. Každý zkušený programátor, elektronik nebo konstruktér byl kdysi začátečníkem, který podobným textům nerozuměl. Rozdíl je pouze v tom, že se nevzdal a pokračoval dál.
Jak říká Martin Malý, bastlení je zvláštní forma činnosti, která díky něčemu zajímavému, zábavnému a motivujícímu vede k rozvoji celé řady inženýrských dovedností. Od prvotního nápadu a návrhu projektu přes práci s hardwarem, programování a elektroniku až třeba k modelování a 3D tisku.
Člověk se učí proto, že chce něco vytvořit, ne proto, že musí splnit zadání nebo napsat test. (To zase říkám já 😊)
Pokud si tedy máte z celého seriálu odnést jedinou myšlenku, pak asi tuto:
ESP32 ani MicroPython nejsou cílem. Jsou pouze nástrojem. Skutečným cílem je vaše vlastní zvědavost, chuť objevovat a radost z toho okamžiku, kdy něco, co ještě včera existovalo jen jako nápad ve Vaší hlavě, dnes skutečně funguje na stole před vámi.Přeji vám proto, aby vám ESP32 nezůstalo ležet v šuplíku a aby MicroPython nebyl jen dalším programovacím jazykem, který jste si vyzkoušeli. Ať se stanou prostředkem k realizaci nápadů, které vás baví, inspirují a možná občas přivedou i k problémům, o kterých jste si mysleli, že je nikdy nebudete řešit.
Protože právě tehdy se člověk učí nejvíc.
A o tom je to „BASTLENÍ“!