Hlavní navigace

Řízení diodové matice na Arduino UNO R4 Wifi (jinak)

3. 11. 2024 13:02 (aktualizováno) Ondřej Novák

V tomto článku si ukážeme jiný způsob řízení LED diodové matice na oblíbeném vývojářském boardu Arduino UNO v revizi 4 Wifi

Diodová matice na Arduino R4 Wifi (Dot Matrix)

Bastlíři si jistě všimli, že minulý rok vyšla nová revize oblíbené vývojové desky s mikročipem Arduino UNO. Nová revize nese označení R4 a prodává se ve dvou variantách. Verze Arduino UNO R4 Minima, verze Arduino UNO R4 Wifi. Já budu tady mluvit hlavně o verzi R4 Wifi, která kromě jiného přináší i diodovou matici 12×8 integrovanou přímo na vývojové desce

Diodová matice

Nejprve krátce ke nové revizi R4. Ta přináší výrazné změny oproti předchozím revizím. Změnu najedte už v samotném procesoru, Arduino tak opouští AVR a nasazuje procesor ARM Renesas. Tam se spousta věci programuje jinak, ale autoři projektu se dušují, že na úrovni API je deska kompatibilní. Zachován byl i tvar a rozložení pinů i jejich význam, který byl dále rozšířen o podporu I2C, SPI nebo CAN bus. Nový procesor má frekvenci 48MHz, 32KB SRAM, 256KB flash a 8KB EEPROM. Více podrobností zde

Já se tady zaměřím na řízení diodové matice, která je další novinkou a která umožňuje  přímo na desce zobrazovat různé informace, pro které bychom jinak museli zařadit samostatný display. Na začátek je třeba říct, že matice 12×8 není žádné ultra rozlišení, kam by se vešlo mnoho údajů. Pokud jde o malování písmen a číslic, tak nejvíc tam zobrazíte 4 číslice v organizaci 2 a 2, zde se použije font 5×3 s 1 pixelem pro padding. Případně 3 číslice na jedné řádce. Lze také text scrollovat, tam se samozřejmě dá zobrazit víc údajů.

Pro řízení diodové matice je k dispozici knihovna (nebo spíš třída) Arduino_LED_Matrix.h, kterou najdete ve standardní výbavě. Zdrojáky této header-only knihovny najdete na tomto odkazu a ten důvod, proč jej sem dávám, právě souvisí s tímto článkem, a ten bude hlavně o tom, proč byste neměli tuto knihovnu používat

Code review Arduino_LED_Matrix.h

Měl jsem potřebu studovat kód téhle knihovny hlavně kvůli simulaci. Pracuji na větším projektu, a mnohem lépe se takový projekt ladí v simulovaném prostředí přímo na PC s v oblíbeném IDE s použitím klasického gdb při ladění a hledání chyb. To ovšem znamená že periferie, vstupy a výstupy je třeba simulovat, ale o tom někdy jindy. Nicméně při studium toho, co tato knihovna dělá jsem se kolikrát zhrozil z toho, co jsem ve zdrojovém kódu viděl.

  • Knihovnu psal spíš C-čkač, nikoliv odborník C++. Je to dáno tím, že používá #define namísto constexpr deklarací. Velká část low level kódu pro Renesas je napsán v C. Proč v roce 2024 se stále low level kód píše v C za použití maker a globálních proměnných?
  • Knihovna nejspíš musela vzniknout někdy během roku 2020 a už tehdy byl k dispozici GCC-7, na kterém běží celé IDE Arduino. A GCC-7 umí normu C++17. Slušelo by se tedy všechny const výrazy nahradit  constexpr  výrazy
  • Hned na prvních řádcích uvidíte zápis static const int. Klíčové slovo static se ale v hlavičkových souborech nesmí vyskytovat, tedy s výjimkou tříd.
  • Divná je deklarace static uint8_t __attribute__((aligned)) framebuffer[NUM_LEDS / 8]; na řádku 147. Opět je tam „static“ který se v hlavičkovém souboru nesmí použít, protože by mohl vést na vícenásobnou deklaraci tohoto bufferu. Chcete mi tvrdit, že máme statický framebuffer?
  • Naprosto no-go deklarace je ovšem na řádku 144. Tady programátor deklaruje makro, které vytváří alias k metodě(!!!). V moderním C++ je použití maker považováno za evil programátorskou techniku, na stejné úrovni jako použití goto nebo příkazu eval v javascriptu. Dobrý programátor makra nepoužívá. Makra se používají jen když je potřeba udělat něco, co nejde zařídit jinak, typicky jsou to generátory kódu, Ale to není tento případ. Deklarace makra způsobí, že kdekoliv se v textu objeví toto makro, je nahrazeno bez ohledu na kontext. Já jsem na to náhodou narazil, když jsem si pro svou funkci vybral jméno renderBitmap a nestačil jsem se divit, jak sprostě mi překladač nadával.
  • Poslední poznámka je ovšem lahůdka. Taková třešnička na dortu. Dobře si prohlédněte následující makro. Schválně, jestli uvidíte problém 
#define renderBitmap(bitmap, rows, columns) loadPixels(&bitmap[0][0], rows*columns)

Máte to? Vidíte to?

Pánové, já nevím, ale tady code review končí. Co je ve zbytku kódu mne nezajímá. Ten člověk okamžitě letí ze zkoušky! I kdyby to byl první řádek v jinak naprosto bezchybném kódu (jako že není)

- pro ty co to nevidí: Parametry v makru je třeba dávat do závorek, protože při expanzi dochází k nahrazení bez znalosti kontextu. Pokud bych makro zavolal jako renderBitmap(bmp,12+5,32+4), bude se makro expandovat na loadPixels(&bmp[0][0], 12+5*32+4). Teď už to je vidět? Tohle bylo první pravidlo, které jsem se naučil ještě na střední v devadesátých letech, když jsem se učil v jazyce C.

(ano, i já umím Céčko :-)

Divné manipulace s daty

Knihovna nejen že nedosahuje programátorských kvalit co se zdrojového kódu týče, ale také provádí nepochopitelné operace s daty. 

K buzení dot matrix se používá časovač s interruptem. Tento časovač se instaluje na řádcích 180–184. Deklaraci begin od FspTimeru najdeme zde.  Z ní se dozvíme, že čtvrtý parametr je frekvence a v knihovně je napsáno 10000. To znamená, že knihovna volá 10000× obsluhu přerušení. Docela velké číslo na řízení 96 diod? Kdyby v každém taktu rozsvěcoval 1 diodu, znamenalo by to obnovovací frekvenci 104 Hz. Tohle snad ale nedělá, že ne?

Bohužel to dělá, program opravdu funguje tak, že v každém přerušovacím cyklu je nejprve vše zhasnuto, (řádek 119) a následně se rozsvítí jedna z 96 diod (řádky 123–129). Celá funkce turnLed je řízena z přerušení, které začíná na řádce 307.

Tam se dozvíme, že diody se prochází zleva doprava a zezhora dolu a stejně tak se prochází bajty ve frame bufferu. Prvních osm diod má přiřazeno 8 bitů prvního bajtu framebufferu a to v pořadí od LSB (vlevo) do MSB (vpravo). Dobře si to pamatujte!

Když totiž nahlédnete do strohé dokumentace, uvidíte tento příklad grafiky:

001100011000
010010100100
010001000100
001000001000
000100010000
000010100000
000001000000
000000000000

Ta je zapsaná jako

unsigned long frame[] = {
  0x3184a444,
  0x42081100,
  0xa0040000
};

Což je asi správně, minimálně na začátku čísla 318 vedou na zápis 001100011000. To co tady nesedí je, že vlevo je MSB a vpravo je LSB. 

Když jsem tohle viděl, nedávalo mi to smysl, až do chvíle, než jsem analyzoval funkci reverse na řádku 133. Tato funkce se volá při kopírování dat do framebufferu, což se děje při každé změně animačního frame.

Moc by mě zajímalo, co se v tu chvíli programátorovi  ho­nilo hlavou, když to analyzoval a vymýšlel. Tak on si zvolil procházení bitů LSB->MSB ale pak na vnějším API inzeruje pořadí MSB->LSB a uvnitř pracně reversuje bity, přestože by stačilo, abych pouze otočil operaci posunu na řádce 309

///původní
turnLed(i_isr, ((framebuffer[i_isr >> 3] & (1 << (i_isr % 8))) != 0));
///změna
turnLed(i_isr, ((framebuffer[i_isr >> 3] & (1 << (7-(i_isr % 8)))) != 0));

Jak budit diodovou matici?

Po tom, co jsem se zhrozil nad výše uvedeným kódem, jsem byl donucen ponořit se do dokumentace procesu Renesas a datasheetu k Arduinu, abych pochopil, jak vlastně ovládání diodové matice funguje. Se svými zjištěními se nyní podělím s vámi. 

Diod je celkem 96 a jsou buzeny pomocí 11 signálů vyvedených z procesoru. Je to 11 portů. které nemají na desce žádné další využití. Diody nejsou zapojené v mřížce , jak by se dalo očekávat, ale používá se charlieplexing. Na klasickou mřížku by bylo potřeba 12+8 = 20 signálů, my jich potřebujeme pouze 11. Podle datasheetu(str 13) jsou zapojené takto

Arduino diody

K tomu je nutno dodat, že ani datasheet neobsahuje správně označené a provázané informace. Například se dozvíme, že řízení se používají piny P003, P004, P011, P012, P013, P015, P204, P205, P206, P212, P213. Na obrázku se ale žádná taková označení nepoužívají, ale zas tam najdu čísla 7,3,4… a dále pak ROW0, ROW1, ROW2. Jaká je mezi tím souvislost. No ROW1 alias 3 je P012. Proč to tam není napsaný? Ušetřilo by mi to několik hodin hledání. Dobře, co je ale P012. To je 12. pin na portu P0. Třeba P204 je 4. pin v na portu P2. Následující řádky tedy začínají dávat smysl

#define LED_MATRIX_PORT0_MASK       ((1 << 3) | (1 << 4) | (1 << 11) | (1 << 12) | (1 << 13) | (1 << 15))
#define LED_MATRIX_PORT2_MASK       ((1 << 4) | (1 << 5) | (1 << 6) | (1 << 12) | (1 << 13))

Pro zhasnutí celé matice stačí použít následující zápis

  R_PORT0->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT0_MASK);
  R_PORT2->PCNTR1 &= ~((uint32_t) LED_MATRIX_PORT2_MASK);

Pokud nechytáte kontext tak v Arduinu jsou R_PORT0 a R_PORT2 globální proměnné. Navíc jsou to makra, která obsahují nějaký šílený pointerový přepočet

Umíme všechno zhasnout, ale umíme to rozsvítit?

Aby se nějaká dioda rozsvítila, tak musíme správně nastavit polaritu na jednotlivých pinech. Můžeme je nastavit na High (5V) nebo LOW(0V). Pokud je matice zhasnuta, jsou všechny piny nastaveny do stavu vysoké impedance. To je princip řízení v charlieplexingu.

Mnou kritizovaná knihovna rozsvítí jednu diodu tak, že jeden z pinů nastaví na HIGH a druhý na LOW

    bsp_io_port_pin_t pin_a = g_pin_cfg[pins[idx][0] + pin_zero_index].pin;
    R_PFS->PORT[pin_a >> 8].PIN[pin_a & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_HIGH;

    bsp_io_port_pin_t pin_c = g_pin_cfg[pins[idx][1] + pin_zero_index].pin;
    R_PFS->PORT[pin_c >> 8].PIN[pin_c & 0xFF].PmnPFS =
      IOPORT_CFG_PORT_DIRECTION_OUTPUT | IOPORT_CFG_PORT_OUTPUT_LOW;

Opět pro kontext. g_pin_cfg je opět globální proměnná, která obsahuje tabulku adres řídících registrů. V té tabulce začínají informace o našich pinech na indexu pin_zero_index což je 28. Od tohoto indexu jsou piny přiřazeny podle čísel na obrázku. Mimochodem, pin_a  (resp pin_c) po vyzvednutí obsahuje číslo portu a číslo pinu v portu. Takže pro pin 7 (ROW0) zde bude 0×0204. Proměnná R_PFS je pointer na řídící registr portů, který je mapován jako pole struktur pinů. Proto nejdřív jdeme na PORT[x] a tam na PIN[y]. Položka PmnPFS přímo nastavuje příznaky daného pinu. Zde nastavujeme:

  • Že jde o výstup: IOPORT_CFG_PORT_DIRECTION_OUTPUT
  • HIGH – IOPORT_CFG_PORT_OUTPUT_HIGH nebo 
  • LOW – IOPORT_CFG_PORT_OUTPUT_LOW

K tomu poznámka: Snažil jsem se najít nastavení vysoké impedance a neuspěl jsem. Nejbezpečnější je tedy použít globální PCNTR1 (viz výše)

Otázkou také je, zda mohu vhodným nastavením pinů rozsvítit více než jednu diodu. A odpověď zní, že mohu. Pokud například nastavím (7) na HIGH a (3) a (4) na LOW, rozsvítí se 1. a 3. dioda. Mohu takto rozsvítit až 10 diod, pokud jeden z pinů nastavím na HIGH a ostatní na LOW. (K tomu poznámka, pokud svítí všech 10 diod, intenzita trochu klesne, je to dáno proudovým omezením na pinech. Není to tak znát, dokud člověk nemá přímé porovnání, například při blikání)

Celou matici naráz rozsvítit nejde. Tohle se nedá ani u zapojení mřížky, tam mohu rozsvítit vždy jednu řádku. Celá matice se zobrazuje po řádcích tak rychle, že lidské oko nevnímá blikání, vnímá to jako souvislé svícení. Stejný mechanismus se používá i zde. Sice dál budu hovořit o řádce, ve smyslu ROWx, ale když se podíváte, jak jsou diody zapojené, tak to zdaleka neodpovídá skutečné řádce. Třeba ROW0 v HIGH obsluhuje diody 2,4,8,14,22,32,44,58,74,92. Fyzicky to vypadá jako náhodně rozházený hrách. Podobně pak vypadají ostatní řádky, ale dohromady tvoří celou matici. Možných kombinací je  11 × 10 = 110 díod, my jich ale máme jen 96. Je třeba dát pozor na neplatné kombinace, jejich aktivací pak dojde k rozsvícení několika diod s různou intenzitou, je to odvozeno od výše uvedeného schématu a záleží na cestě od HIGH do LOW, kterými diodami se proud může vydat.

Vyzbrojen výše uvedenými znalostmi jsem se rozhodl, že naprogramuji vlastní driver za použití modernějších postupů v C++ programování – tedy za předpokladu, že mi stačí C++17

Knihovna DotMatrix

Knihovnu najdete na githubu a jedná se o header only knihovnu, kterou do Arduino-IDE nainstalujete podobně jako ostatní knihovny. Do libraries vytvoříte adresář /DotMatrix/ a do něho nahrajete obsah. Ve sketchi pak uvedete 

#include <DotMatrix.h>

Šablony

Protože kód je určen pro mikročip, ve kterém máme 32KB SRAM paměti, snažil jsem se maximálně používat constexpr deklarace. Tyto deklarace mají tu výhodu, že část i nebo celý výpočet proběhne během překladu a do výsledné binárky se zapíše až výsledek. Navíc výsledek je pak zapsán ve flash ROM, a nezabírá drahou SRAM. Tohle se hodí zejména pro generování různých map a to budeme potřebovat i zde. Aby bylo možné tyto generátory konfigurovat, je třeba nastavení předávat v parametrech šablon. Šablony jsou tedy těžištěm celé knihovny.

DotMatrix::FrameBuffer<width, height, format, order>

Ač by se mohlo zdát, že velikost framebufferu je předem daná, je to 12×8, tak proč si zavírat dveře k virtuálním frame bufferu s posuvným oknem. Budeme chtít specifikovat která část frame buffer se aktuálně má zobrazovat. To pak umožňuje vytvářet stránky. Parametry width a height jsou v pixelech , format je enum DotMatrix::Format a order je enum  DotMatrix::Order

using MyFB = DotMatrix::FrameBuffer<12,8,DotMatrix::Format::monochrome_1bit>;

Můžeme také chtít zobrazovat frame buffer na výšku s virtuální výškou 1000 řádků

using MyFB = DotMatrix::FrameBuffer<8,1000,DotMatrix::Format::monochrome_1bit>;

Vedle monochromatického formátu máme ještě formát dvoubitový s možností volbu intenzity a blikání

Format::gray_blink_2bit
00 - black
01 - low intensity
10 - high intensity
11 - blink

(nutno dodat, že i když nižší intenzita je 50%, ve výsledku se jeví spíš jako 70–80%, v závislosti na světelných podmínkách)

Poslední parametr Order umožňuje změnit pořadí bitů.

  • Order::msb_to_lsb - MSB je vlevo, LSB je vpravo
  • Order::lsb_to_msb - LSB je vlevo, MSB je vpravo

Pokud chci deklarovat frame buffer, který má format kompatibilní s grafikou použitou v dokumentaci Arduina, deklaruji jej takto:

using ArdFB = DotMatrix::FrameBuffer<12,8,
                           DotMatrix::Format::monochrome_1bit,
                           DotMatrix::Order::lsb_to_msb>;
ArdFB framebuffer;

Instanci framebufferu můžeme vytvořit jako globální proměnnou, stejně jako v předchozím příkladě. Jakmile máme takovou proměnnou, můžeme volat set_pixel, get_pixel, případně přístupovat přímo na pixels, což je bajtové pole.

DotMatrix::Driver<FrameBuffer, orientation>

Tato šablona představuje vlastní driver, který rozsvěcuje jednotlivé diody. Opět je šablona použita k nastavení parametrů. Driver je konfigurován přímo pro konkrétní variantu framebufferu a zvolené orientaci. Volba orientace na driveru umožní implementaci otáčení třeba v závislosti na natočení zařízení, pokud máte k dispozici polohové čidlo. (jen tak mimochodem, k tomu budete potřebovat 4 drivery, každý pro jinou orientaci a podle polohy je přepínat)

using MyDriver = DotMatrix::Driver<MyFB,DotMatrix::landscape>;
constexpr MyDriver mydriver={};

Proměnnou driveru deklarujeme vždycky jako constexpr. V konstruktoru driveru se totiž sestavuje mapa jednotlivých pinů a kombinací. Podoba výsledné mapy je ovlivněna nastavením rozměrů framebufferu, orientace, formátu i uspořádání bodů v rámci bajtů. Veškerý výpočet mapy provádí překladač, který do výsledné binárky zapíše až výslednou mapu.

Tato mapa obsahuje 110 záznamů PixelLocation:

    struct PixelLocation {
        uint8_t offset;
        uint8_t shift = 8; //posun o 8 => 0 = pro neplatnou kombinaci
    };

Pro každou kombinaci High/Low 11 signálů (což je 110, protože symetrické kombinace, např. 7–7, jsou neplatné a nedávají smysl) je poznamenán offset bajtu, ve kterém se pixel nachází a posun (vpravo), tak aby se pixel dostal do dosahu jeho masky. Na té se pak testuje, zda dioda svítí nebo ne. Vlastní řízení je potom snadné, prostě se prochází všechny kombinace a aktivují se diody korespondující s aktivními bity.

Volání driveru

Aby se něco zobrazilo, je potřeba driver volat. Pro 1 bitový frame buffer je třeba driver volat aspoň 500× za sekundu. Pro 2 bitový frame buffer je doporučená frekvence 1000× za sekundu (protože low intensity se realizuje poloviční dobou svitu). V obou případech je framerate 45 fps.

#include <DotMatrix.h>

using MyFB =  DotMatrix::FrameBuffer<8, 96*6, DotMatrix::Format::monochrome_1bit>;
using MyDriver = DotMatrix::Driver<MyFB, DotMatrix::Orientation::portrait>;

MyFB myfb;
constexpr MyDriver driver = {};
DotMatrix::State st = {};


void setup() {
  char all[110] = "Hello world! ";
  for (int i = 0; i < 96; ++i) all[i+13] = i+32;
  all[110] = 0;
    DotMatrix::TextRender<DotMatrix::BltOp::copy, DotMatrix::Rotation::rot90>
      ::render_text(myfb, DotMatrix::font_6p, 7, 15, all);
}

void loop() {
  delay(2);
  driver.drive(st,myfb,st.counter / 50); //offset ve frame bufferu - scrolluje

}

Volání driveru z přerušení

AKTUALIZACE: Následující odstavec už není pravdivý. Knihovna v revizi acdd82a dostala funkci enable_auto_drive která spustí driver pomocí timeru a přerušení. Celé nastavení řízení lze tedy provést ve funkci  setup()

void setup() {
  DotMatrix::enable_auto_drive(driver, st, framebuffer);
}

Původní text:

Knihovna DotMatrix nemá přímou podporu řízení z přerušení, ale s použitím FspTimer to lze snadno zajistit.

FB framebuffer;
constexpr DotMatrix::Driver<FB,DotMatrix::Orientation::portrait> driver;
DotMatrix::State st;
FspTimer ledTimer;
void timer_callback(timer_callback_args_t __attribute((unused)) *p_args) {
driver.drive(st, framebuffer);
}
void beginTimer() {
uint8_t timer_type = GPT_TIMER;
int8_t tindex = FspTimer::get_available_timer(timer_type);
ledTimer.begin(TIMER_MODE_PERIODIC, timer_type, tindex, 440, 0.0f, timer_callback);
ledTimer.setup_overflow_irq();
ledTimer.open();
ledTimer.start();
}
void setup() {
beginTimer();
}

Rozdíl mezi Arduino_LED_Matrix a DotMatrix

Rozdíl ve způsobu řízení diod je vidět na první pohled. Diody řízení knihovnou DotMatrix mají vyšší svit a obrazec je dobře vidět i denním světle. Je to dáno tím, že dioda svítí 10× déle (1/96 s original, 1/10 s DotMatrix)

Malou nevýhodou je kolísavý jas, který může být lehce znatelné v animacích, které aktivují v každém frame různý počet diod. Toto se může projevovat jako šum v pozadí.

Zobrazování bitmap a textu

Knihovna DotMatrix dále nabízí jednoduché nástroje pro práci s bitmapou a s textem. Pro kreslení písmen je potřeba použít font, ve kterém jsou jednotlivá písmena definovaná jako bitmapy – proto podpora bitmap.

Bitmapu deklarujeme opět ideálně jako constexpr

//DotMatrix::Bitmap<width,height>
constexpr DotMatrix::Bitmap<9,7> srdce=" XX   XX "
                                       "X  X X  X"
                                       "X   X   X"
                                       " X     X "
                                       "  X   X  "
                                       "   X X   "
                                       "    X    ";

I v tomto případě je doporučeno použít constexpr, protože překladač je schopen provést konverzi asciiartové formy do binární během překladu. Platí, že mezera ve stringu znamená nulu a jakýkoliv jiný znak znamená jedničku.

Bitmapu lze přenést do frame bufferu pomocí operace BitBlt

void ukaz_srdce(FB &fb) {
    using namespace DotMatrix;
    BitBlt<BltOp::copy, Rotation::rot0>::bitblt(fb, srdce, 2, 1);
}

Zobrazení textu

K dispozici je DotMatrix::font_6p představující font vysoký 6 řádků (+1 řádek margin), proporcionální šířka, dále font DotMatrix::font_5x3 představuje font 5 řádku vysoký a 3 řádky široký (+1 řádek a sloupec margin)

Pro kreslení písma je dobré si vytvořit vlastní instanci šablony TextRender

using MyTextRender = DotMatrix::TextRender<DotMatrix::BltOp::copy,
                                     DotMatrix::Rotation::rot0>;

V rámci této třídy máme k dispozici statickou funkci  render_text(fb, font, x, y, "text")

Následující kód kreslí text orientovaný „dolů“

 DotMatrix::TextRender<DotMatrix::BltOp::copy, DotMatrix::Rotation::rot90>
      ::render_text(myfb, DotMatrix::font_6p, 7, 15, str);

Jen drobné upozornění, fonty pokrývají pouze ascii znaky 32–127. Češtinu si budete muset doprogramovat sami. Písmo se spíš hodí pro zobrazování čísel a symbolů, například výsledky měření, podle toho, k čemu budete Arduino potřebovat.

BitBlt operace

  • copy - kopie bitmapy
  • and_op – smaže body, které v bitmapě jsou nastavené na 0
  • or_op – nastaví body, které v bitmapě jsou nastavené na 1
  • xor_op - invertuje tam, kde je v bitmapě nastavena 1.

Závěr

Původně jsem neměl v plánu se něčím takovým zabývat, ale úroveň kódu, který jsem nalezl v produkčním prostředí mě nenechala klidným. Smutné je, že toto není ojedinělý případ. Jeden příklad za všechny, ve Wiringu (Arduino API) vestavěný objekt Serial ve skutečnosti neexistuje, je to alias/makro k _UART0. Proč? – teď malinko popudím Rustaře – ale když vidím úroveň programování Céčkařů, tak se vám ani nedivím. Pořád ale nechápu, proč musel vzniknout nový jazyk?

Z dalších témat, které mám v „backlogu“ – rád bych si například posvítil na implementaci EEPROM api na Arduino R4. I tam najdeme pěkné šmakulády. 

Kod knihovny najdete na githubu. Dotazy pište do komentářů

Zobrazování teploty od dvou teploměrů na diodové matici.
Zobrazení měření dvěma teploměry na diodové matici


Příklad rolujícího textu

Sdílet