Arduino pod pokličkou: jak funguje mikrokontrolér
V dnešním článku se budeme zabývat tím, jak je vůbec možné, že Arduino umí vykonávat program, který do něj nahrajeme. Představíme si jednotlivé komponenty jeho mikrokontroléru a stručně si vysvětlíme jejich funkčnost. Budeme se sice věnovat mikrokontroléru ATmega328P, který nalezneme například na deskách Arduino UNO a NANO, ale představené části většinou v různých obměnách nalezneme ve všech mikrokontrolérech.
Tento článek z vás neudělá hardcore geeka, který zná nazpaměť strojový kód ATmegy, ale snad vám trochu poodkryje některé koncepty, které se při programování hardware hodí znát.
Obsah článku:
Úvod
Arduino je super věc. Na tom se asi shodneme. Je vhodné pro začátečníky i pokročilé, hodí se na rychlé prototypování a ověření všemožných konceptů. Bezesporu se na něm dají postavit i velice zajímavé projekty.
Zaměření na zpřístupnění Arduina velkému spektru uživatelů ale s sebou nese i několik problémů. A nejedná se jenom o ztrátu výkonu. Je to například i fakt, že uživatel ztrácí povědomí o tom, na jakém hardware jeho program běží. Pak se může stát, že se mu perou knihovny, protože obě využívají stejný časovač apod. Nebo se diví, že se mu program zasekává, nebo funguje podivně, ale už mu nedojde, že se snaží používat víc paměti, než je na čipu dostupné.
Pojďme si tedy zvednout pomyslnou pokličku a podívat se, co všechno v takovém mikrokontroléru najdeme.
Arduino UNO
Jádrem desky Arduino UNO je mikrokontrolér ATmega328. To je ten největší černý šváb na desce. On samotný stačí k tomu, abyste na něm rozběhli svoje programy. K nahrání programu do samotného čipu potřebujete programátor, který připojíte k určeným pinům čipu. Aby ale bylo nahrávání programů do čipu o něco pohodlnější, přidali autoři Arduina na desku ještě další komponenty, mimo jiné USB převodník. Ten slouží jako takový překladatel mezi USB a čipem ATmega328. V Arduinu je také nahraný takový pomocný program, bootloader, který se stará o správné nahrávání nových programů. O něm více v části o Flash paměti.
ATmega328P
Představme si nyní, že Arduino neexistuje a my máme před sebou čip ATmega328 se zadáním, že na něm máme rozběhnout nějaký jednoduchý program. Co bude první věc, kterou uděláme? Podíváme se do datasheetu! To je dokument, ve kterém najdeme všechny potřebné informace o čipu.
Na obrázku výše vidíme blokové schéma mikrokontroléru. V jeho jádru je procesor řady AVR, ke kterému jsou v mikrokontroléru připojeny další potřebné komponenty, jako jsou například paměti, časovače a další. Procesor samotný se stará o běh programu, v rámci jehož provádění může využívat právě připojené komponenty.
V datasheetu také objevíme následující zjednodušené schéma mikrokontroléru, kolem kterého se bude tento článek točit.
Nejdříve se podíváme na různé paměti, které v čipu najdeme.
Paměti v ATmega328
S paměťmi to není tak jednoduché, jak by se na první pohled mohlo zdát. Je to kvůli tomu, že jich na čipu najdeme vícero.
Registry
Registry jsou nejmenší a nejrychlejší paměti, které na čipu najdeme. Jejich funkce je velice důležitá, protože pomocí některých z nich probíhá řízení celého čipu a jeho periferií. Patří mezi ně následující registry:
- status and control – je sada registrů, pomocí kterých probíhá řízení čipu a jeho periferií a získávání zpětné vazby z nich. Mohou to být například registry na řízení časovačů, uspávání čipu a další.
- I/O registry – slouží k řízení vstupně-výstupních operací. Mimo jiné se jejich pomocí ovládají digitální piny.
- program counter – uchovává adresu instrukce programu, která má být provedena. Je to tedy jakési ukazovátko do paměti Flash, které říká, jaká část programu se zrovna provádí (viz. dále). Jeho velikost je 14 bitů.
- instruction register – je do něj nahrána instrukce z paměti Flash s adresou, která je aktuálně uložena v program counter registru
- GPR – general purpose registers – neboli registry pro obecné použití. Jsou osmibitové registry, které může programátor využívat při tvorbě programů (a samozřejmě je využívá i kompilátor, když překládá program z C/C++ do assembleru). Těchto registrů je 32 a jsou pojmenované r0 až r31. Když chceme například sečíst dvě osmibitová čísla, nahrajeme je do registrů r1 a r2 a zavoláme instrukci add r1 r2 (výsledek se uloží do r1). Protože je registrů omezený počet, je potřeba s nimi šetřit a dělá to tak i kompilátor. Pokud je v registru nějaká hodnota, kterou budeme dále potřebovat, můžeme ji nechat v registru (ale potom nemůžeme registr používat, protože bychom si hodnotu přepsali), nebo ji můžeme uložit do paměti SRAM a znovu ji načíst, až bude potřeba.
Paměť flash
Je paměť, ve které je uchovaný program a je tedy non-volatilní, tedy v ní data zůstávají i po odpojení napájení. Když si koupíte Arduino, tak by jeho Flash paměť měla obsahovat dva programy – tzv. bootloader a program blikání LEDkou. Bootloader je speciální program, který se začne provádět vždy po restartu desky (nebo připojení k napájení). Ten po nějakou dobu poslouchá na sériové lince, jestli mu náhodou USB převodník nepošle nějaké příkazy. V tento moment mohou nastat dvě situace:
- Spustili jsme nahrávání programu, tudíž USB převodník do čipu začne posílat kompilovaný program. Na straně čipu ho ze sériové linky čte bootloader a ukládá na správné místo do Flash paměti. Tím dojde k přehrání předchozího nahraného programu v paměti. Bootloader ale v paměti zůstává (kdyby tam nebyl, nejde jednoduše nahrávat programy přes USB převodník).
- Žádný program se nenahrává. V tuto chvíli bootloader chvilku počká, jestli se program nezačne nahrávat a když se nic neděje, spustí poslední uložený program v paměti.
Ve článku Paradigmata programovacích jazyků jsem zběžně vysvětlil, co je to strojový kód. Jsou to nuly a jedničky, podle kterých umí procesor zjistit, co má dělat. Takovéto kombinace nul a jedniček nazýváme instrukce. V jedné instrukci je u ATmega328 16, nebo 32 bitů (nul/jedniček), tedy 2/4 byty. Velikost Flash paměti ATmega328P je 32KB, ale protože jsou instrukce dlouhé 2/4B, je organizována jako 16KB x 2B. K adresaci 16KB adres paměti je potřeba 14 bitů (214 = 16384B = 16KB). Tomu odpovídají hexadecimální rozsahy adres paměti 0x0000 až 0x3FFF. Jedna instrukce pak zabírá jedno, nebo dvě adresní místa.
Paměť EEPROM
Druhou non-volatilní pamětí na čipu je paměť EEPROM. Tu můžeme u Arduina použít k zapisování hodnot v průběhu běhu programu. Hodnoty v ní zůstanou uložené i při odpojení napájení. Je zde spíše jako pomocná paměť, protože není k běhu mikrokontroléru nezbytně nutná. Její kapacita je 1KB.
Paměť SRAM
Je hlavní paměť, kterou program při svém běhu využívá. Jsou na ní uložené například proměnné, které dle potřeby kopírujeme do registrů a zase zpátky, aby s nimi bylo možné provádět výpočty. Je velká 2KB a její adresy jsou od 0x0100 do 0x08FF. Nejnižší paměťová buňka nemá adresu 0x0000, protože ATmega328 do jednoho adresového prostoru s adresami 0x0000 až 0x08FF (viz obrázek) spojuje registry a SRAM paměť. Z pohledu programu se tak tyto adresy jeví jako jednolitý prostor, i když část tvoří registry a část SRAM paměť.
Na nejnižších adresách najdeme již představené registry r0 až r31. Dále následují registry pro ovládání vstupů a výstupů.
I/O Registers slouží k řízení vstupně-výstupních operací. Pro manipulaci s těmito registry jsou určené speciální instrukce (IN, OUT, …). V tomto případě používají vlastní adresování 0x00 až 0x3F. Je s nimi ale možné manipulovat pomocí instrukcí, které používáme i k práci s ostatními registry. V tomto případě mají adresy jako na obrázku.
S registry označenými jako Ext I/O Registers nelze manipulovat speciálními instrukcemi, jako s předchozí skupinou registrů a musíme využít instrukce pro práci s normálními registry. I tyto registry slouží k obsluze vstupně-výstupních zařízení.
Aritmeticko-logická jednotka
Tuto jednotku na obrázku najdeme pod zkratkou ALU (arithmetic logic unit). Stará se o matematické operace (+, -, …), bitové operace, umí počítat jednotkové a dvojkové komplementy a další.
Když se podíváme do blokového schématu čipu, zjistíme, že jako vstupy ALU používáme GPR registry – vzpomeňme si například na již zmíněný příklad se součtem dvou hodnot v registru: add r1 r2. Jako jeden z operandů se nastaví registr r1, jako druhý se nastaví r2, sečtou se a výsledek se pošle na datovou sběrnici (tlustá čára), odkud se opět zapíše zpět do registru r1 (výsledek instrukce add se implicitně ukládá do prvního z operandů).
Dekodér instrukcí
V obrázku instruction decoder. Je speciální kus hardware, který se stará o to, aby byly v procesoru zapojené všechny komponenty, které jsou potřebné pro provedení aktuální instrukce. Funguje tak, že se podívá do instruction register, kde najde instrukci, která se má provést, a podle toho aktivuje patřičné řídicí signály v procesoru.
Když se opět vrátíme k instrukci add r1 r2, tak v jejím případě dekodér způsobí to, že se na jeden vstup ALU připojí registr r1, na druhý se připojí r2, ALU se nastaví tak, aby prováděla sčítání a výsledek se pošle zpět do registru r1. Za tohle všechno je odpovědný dekodér instrukcí.
Další části
Podrobnější popis dalších částí je již mimo rozsah tohoto článku. Pojďme si alespoň některé z nich zběžně představit.
Interrupt unit obstarává řízení přerušení. To jsou speciální signály, které mohou procesor „vyrušit“ od provádění aktuálního programu. Tato přerušení mohou pocházet například od digitálních pinů, vnitřních časovačů, nebo od tlačítka reset. Přerušení jsou také způsob, kterým o sobě mohou procesoru dát vědět různé periferie – například analogový převodník, watchdog, SPI jednotka a další.
SPI unit slouží k obsluze SPI komunikace. Bez této jednotky by musel procesor zpracovávat komunikaci v programu, což by ho brzdilo. Díky této jednotce ale komunikace probíhá mimo hlavní program a jednotka o sobě dává vědět jen například v případě příchozích dat. Kromě této jednotky se o komunikaci starají i například jednotky pro USART (obsluha sériové linky) a TWI (I2C).
Watchdog timer je časovač, který slouží k hlídání správné funkce čipu. Lze ho použít k tomu, aby sledoval běh programu. Pokud program trvá moc dlouho bez toho, aby watchdogu potvrdil, že se nezasekl, watchdog mikrokontrolér restartuje. Kromě něj obsahuje čip ještě trojici časovačů, které slouží k vyvolávání přerušení po určité době. Jsou to časovače 0, 1 a 2, přičemž 0 a 2 jsou osmibitové, časovač 1 je šestnáctibitový.
Analog comparator slouží k porovnání dvou analových hodnot a využívá ho i například jednotka analogového převodníku. Převod z analogové na digitální hodnotu ale zabere celkem dost času, tak aby procesor nemusel na výsledek aktivně čekat, je pro něj vyhrazena vlastní jednotka, která dává o výsledku vědět pomocí přerušení.
To byl takový rychlý úvod do problematiky kolem mikrokontrolérů. Pokud máte jakékoliv otázky, nebojte se zeptat v komentářích nebo třeba na fóru https://arduino-forum.cz.
Poznámka: Pokud vás zajímají hlubší detaily o tom, jak mikrokontrolér funguje na nižších úrovních, doporučuji českou knihu od Martina Malého: Hradla, volty, jednočipy.
- Sledovač slunce s Arduinem - 23.3.2022
- Programovatelný kytarový pedál s Arduinem - 26.2.2020
- Arduino infračervený teploměr vytištěný na 3D tiskárně - 11.2.2020