neděle 5. března 2017

Arduino - vzdálené programování

Před pár hodinami jsem měl přednášku na Installfestu, kde jsem se snažil shrnout své několikaleté zkušenosti se vzdáleným programování Arduin přes Bluetooth, WiFi a Ethernet. Cílem tohoto článku je doplnit onu přednášku o konkrétní čísla, útržky programů a další drobnosti, které se do přednášky z časových, technických či jiných důvodů nedostaly:


Zde je odkaz na mou prezentaci v PDF.

Bootloader a pojistky

Jádrem přednášky byla "teorie" o bootloaderech - krátkých prográmcích, které jsou uložené ve vyhrazené části Arduino flash paměti a které se podle nastavení fuses ("pojistek") spouštějí po zapnutí či resetu Arduina, resp. mikrokontroléru ATmega328p.

Pojďme se proto podívat, co všechno fuses umějí u ATmegy328p nastavit. Na kalkulačce vybereme "AVR part name" ATmega328P a už to vidíme:

Toto nastavení platí pro "standardní" hodnoty pojistek u Arduina s bootloaderem Optiboot - tyto hodnoty jsou Low=FF a High=D6 (což se dá předvyplnit dole na stránce, pokud známe hexadecimální hodnoty pojistek a chceme se podívat, co ty hodnoty znamenají v lidské řeči).

Čteno shora dolů tam máme: externí krystal s frekvencí 8 nebo více MHz (což je OK, Arduino má standardně 16 MHz krystal). Dále je zapnutý Boot Reset vector, což je důležité, pokud chceme, aby se po resetu skočilo do bootloaderu. A v rozbalovacím menu je vybrána velikost Boot Flash sekce "256 slov". Jak jsem na přednášce stihl říct, 256 slov se rovná 512 bajtům.

Zároveň vidíme, kam bude mířít ten Boot Reset vector - na adresu $3F00, což je decimálně  16128, ale i toto číslo je v 16bitových slovech, takže v bajtech se jedná o adresu 32256. Na tuto adresu budeme chtít nahrát náš bootloader, protože sem po zapnutí či resetu skočí mikrokontrolér pomocí onoho Boot Reset vectoru. Mimochodem, adresa 32256 je právě 512 bajtů od konce 32kB (32768bajtové) flash paměti, což odpovídá tomu, že bootloader je vždy uložen na nejvyšších adresách flash paměti.

Z dalších nastavení "pojistek" vidíme, že při "Chip Erase" (mazání čipu) nedojde ke smazání paměti EEPROM (což je dobře, pokud v ní máme uložené nějaké vlastní hodnoty), a poslední zapnutou volbou je SPI programování, což je naprosto klíčové. Pokud byste si vypnuli toto SPI programování, anebo o dvě volby níže si zapnuli vypnutí resetu (Reset Disabled), tak už standardně daný mikrokontrolér  nebudete schopni naprogramovat a pak je poslední záchranou na přednášce zmíněné paralelní vysokonapěťové ("parallel high-voltage") programování.

Jinými slovy: pokud chcete zachovat běžnou funkčnost Arduina, neměňte nic než nastavení velikosti Boot Flash sekce.

Při každé změně kteréhokoliv nastavení se ihned přepočítají všechny odpovídající hodnoty na celé stránce, takže dole neustále vidíte výsledné správné hodnoty pojistek a dokonce i parametry příkazu avrdude, kterými dané pojistky zapíšete do ATmegy. Takže si zkusíme třeba změnit velikost Boot Flash sekce z 256 slov na 512 slov (což je nutné, pokud potřebujeme zapsat bootloader delší než 512 bajtů, a kratší než 1024 bajtů, jako je případ Optibootu upraveného pro Ethernet). Vidíme, že adresa Boot startu se změnila na $3E00, což je decimálně a v bajtech 31744. Hodnoty pojistek se změnily (dole na stránce kalkulačky) následovně:


Čili Low zůstává FF, ale High se změnila z D6 na D4. Vedle v "AVRDUDE arguments" jsou vidět i všechny parametry pro avrdude, které dané hodnoty zapíší.

To znamená, že pokud použijete jiné Arduino jako programátor ("Arduino as ISP"), jak jsem na přednášce pro začátek doporučoval, celý avrdude příkaz (nastavení pojistek + vypálení bootloaderu Optiboot s úpravou pro Ethernet) bude vypadat následovně:

avrdude -p m328p -c avrisp -P /dev/ttyUSB0 -b 19200 \
 -U lfuse:w:0xff:m -U hfuse:w:0xd4:m \
 -U flash:w:./optiboot_atmega328.hex

Tak. Pokud použijete Arduino UNO jako programátor, tak je možná vašemu systému známé jako zařízení /dev/ttyACM0 místo /dev/ttyUSB0, ale to je kosmetický detail, se kterým si poradíte (protože tuto hodnotu vidíte krom jiného i v Arduino IDE, když si vybíráte sériový port v menu Nastavení).

Programátor "Arduino as ISP"

Jak z Arduina udělat ISP programátor pro jiná Arduina či Atmel AVR čipy je popsáno na mnoha stránkách - např. přímo od Arduina tady. Princip je jednoduchý: nejdřív do Arduina nahrajeme z Arduino příkladů program zvaný "ArduinoISP" a teprve poté zapojíme 10uF kondenzátor mezi RESET tohoto Arduina a zem. Díky kondenzátoru při příštím nahrávání programu z Arduino IDE nedojde k obvyklému resetu programovacího Arduina, což je moc dobře, protože my nechceme nahrávat nový program do něj, ale jen ho poslat skrz něj do dalšího Arduina, zapojeného za ním přes SPI port.

Sám jsem si postavil vlastní ISP programátor z jednoho nepoužívaného Arduino Nano. Na fotce je vidět i ten 10uF kondenzátor. Jak tak studuju to zapojení na té univerzální destičce, zdá se mi, že mám ten kondenzátor celou dobu zapojený špatně! Mám ho mezi RESET a VCC místo GND. Zajímavá chyba, musím to předělat:


Softwarový reset

Jak jsem na přednášce zmínil, bootloader se dostane ke slovu vždy po zapnutí či resetu Arduina/ATmegy328p. Proto pokud chceme nahrávat nový program, potřebujeme Arduino resetovat. Normálně se toto děje pomocí signálu sériového portu DTR (Data Terminal Ready - z doby, kdy k sálovým počítačům byly připojené terminály), kterýžto je přes kondenzátor připojen přímo na RESET signál Arduina.

Při programování přes WiFi jsem doporučil připojit pin GPIO0 od ESP8266 přímo na RESET pin Arduina - potom firmware esp-link automaticky resetne před programováním a vše funguje transparentně, jak jsme z Arduino IDE zvyklí. Pokud ale programujeme Arduino vzdáleně přes Bluetooth nebo přes Ethernet, nemáme jaksi po ruce signál DTR, takže si musíme pomoci jinak a Arduino resetovat softwarově.

K softwarovému resetu zneužijeme HW watchdog - nezávislého hlídače vestavěného do každého procesoru ATmega. Nejkratší kód, který zajistí reset celého mikrokontroléru, vypadá takto:

    wdt_enable(WDTO_250MS);
    while(1);

Funkcí wdt_enable() nastavíme čas, do kterého musíme watchdog counter resetnout, jinak resetne on nás (250 milisekund), a pak neděláme nic (while(1); se zacyklí na místě), takže watchdog za 250 milisekund skutečně resetne celý mikrokontrolér, což jsme přesně chtěli.

Pozor na starší bootloadery (dnes už jen z Číny na klonech Arduin), které zapomínaly watchdog vypnout. V takovém případě se pak vlastně Arduino donekonečna samo restartuje každou čtvrtsekundu. Pokud ale nejdříve nahrajete Optiboot (tj. ten krásný nový krátký bootloader), tak s ním problém při SW resetu watchdogem nenastává, protože má jako jednu z prvních instrukcí vypnutí watchdogu.

V případě, že programujeme Arduino přes Ethernet, musíme ještě nastavit příznak v EEPROM, aby bootloader věděl, že chceme začít nahrávat novou verzi firmware a počkal na data proudící po síti. Pak celý kód (zapsání příznaku a resetnutí Arduina) vypadá takto:

    EEPROM.update(0x22, 0x55);
    wdt_enable(WDTO_250MS);
    while(1);

Nastavení IP adres v EEPROM

Při programování Arduina přes Ethernet, jak vymyslel a popsal Will Soberbutts, je potřeba do paměti EEPROM připravit sadu IP adres tak, aby bootloader po startu věděl, jak má nastavit Ethernet shield. Osobně pro to používám následující kód:

void setup_ethernet_bootloader_address()
{
    byte addr[] = {
        192, 168, 1, 1,     // gateway
        255, 255, 255, 0,   // netmask
        0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED, // MAC
        192, 168, 1, 102    // address
    };
    for(byte i = 0; i < sizeof(addr); i++)
        EEPROM.update(i + 0x10, addr[i]);
}

Tuto funkci je dobré zavolat hned ze setup(void) funkce. Samozřejmě je nutné nastavit ty adresy podle situace ve vaší síti. MAC adresa je vymyšlená ("DeadBeefFeed") a funguje dobře. Jen pokud máte doma víc Arduin s Ethernetem, je nutné každému přiřadit jinou MAC adresu - stačí měnit jen poslední číslici,  začátek (0xDE) neměňte.

Bezpečnost

Přednáška pominula otázku bezpečnosti celého konceptu vzdáleného nahrávání nového firmware do Arduina. Pravda je, že na Ethernetu na to trošku hřeším - spoléhám se na to, že se mi doma nikdo zlý do sítě nepřipojí, že k ní prostě fyzicky nebude mít přístup. Bezdrátová připojení (Bluetooth/WiFi) jsou zřejmě zranitelnější ("tati, proč na naší ulici už týden parkuje ta černá dodávka se zatmavenými skly?"), takže by to něco chtělo. Nejjednodušší by zřejmě bylo firmware zašifrovat na PC, odeslat šifrované a rozšifrovat bootloaderem předtím, než dojde k uložení do flash paměti Arduina, ale do toho jsem se zatím nepouštěl. Vyžadovalo by to větší zásahy do bootloaderu, hodně času na ladění atd. Možná se k tomu dostanu někdy časem.

Závěr

Programovat Arduino vzdáleně je největší pohoda. Mimochodem, je to vlastně důvod, proč jsem z Arduin nepřešel na STM32 - tam jsem totiž možnost vzdáleného programování přes Ethernet vůbec nenašel. Proto všem doporučuji zkusit si to doma - jakmile to rozjedete, už nebudete chtít jinak :-)

EDIT: 5.3. v 19:30 doplněno pár drobností.

4 komentáře:

  1. Ahoj,
    Ještě k přednášce, ve které to nebylo zmíněno. Do toho Arduina, kterým to budeme programovat, je nutné nahrát ArduinoISP sketch, který je v examples.

    Všude na webu, i ty jsi to zmiňoval v přednášce, je napsáno o nutnosti dát 10uF kondenzátor mezi RESET PIN a GND. Já ho nepotřeboval, když jsem programoval čistý ATmega328p na breadboardu.

    Jinak ATmega328p lze provozovat i bez externího oscilátoru, je potřeba ale patřičně nastavit eFuse, aby byl použit 8MHz interní. Jak moc je ale přesný nedokážu posoudit. Na časově nekritické akce by ale měl dostačovat, čímž ušetříme 3 součástky (oscilátor a 2X 22pF kondenzátor).

    OdpovědětVymazat
    Odpovědi
    1. díky, doplním. Internímu oscilátoru se chci brzy věnovat ve zvláštním blogpostu o ATtiny85.

      Vymazat
    2. 10 uF sa spomína všade, ale podľa mojich skúseností s Arduino Pro Mini ako ISP nie je potrebný. Je dosť možné, že sa tie informácie týkajú možno nejakej staršej revízie Arduina, kde bol reset zapojený odlišne.

      Vymazat
  2. BitBurner - https://sourceforge.net/projects/bitburner
    Jo a hezkej kodan.

    OdpovědětVymazat