Warning
This page is located in archive. Go to the latest version of this course pages. Go the latest version of this page.

10. MBED a Nucleo

Cílem cvičení je vyzkoušet programování kitu Nucleo STM32F401RE v programovacím prostředí MBED. K jednoduchým experimentům postačí účet na webu mbed.com, pro vážnější práci lze využít např. Visual Studio Code s rozšířením PlatformIO, MBED IDE, nebo libovolné jiné podporované prostředí včetně pipeline v příkazové řádce. Více lze nalézt na dedikované stránce.

Kostra programu

V embedded světě je podobně jako ve světě větších počítačů, základem programu v C funkce main(). Malý rozdíl je v tom, že tato funkce nezpracovává argumenty příkazové řádky (není odkud je vzít) a nebude pravděpodobně vracet žádnou návratovou hodnotu - jednak ji není kam vracet a navíc funkce main() by měla obsahovat nekonečnou smyčku. Přítomnost nekonečné smyčky je celkem logická - program mikrokontroléru by neměl nikdy skončit, pouze vykonává jednotlivé podprogramy, vyvolané např. přerušením.

#include "mbed.h"
 
int main()
{
    // inicializace 
 
    while (1)
    {
        // hlavní program
    }
}

Vstupně / výstupní brána

Komunikace embedded prostředků s okolním světem je zajištěna pomocí vstupně / výstupní brány, v anglickém názvosloví GPIO (General Purpose Input Output) rozhraní, nad kterým mohou být postaveny další fyzické a transportní vrstvy (např. přístrojové sběrnice).

Mikrokontrolér STM32F401, který je srdcem kitu Nucleo, má pět 16b GPIO bran (A-E), jejichž jednotlivé piny (0-15) mohou pracovat v některém z následujících módů:

  • digitální vstup, popsán třídou DigitalIn
  • digitální výstup, popsán třídou DigitalOut
  • analogový vstup, popsán třídou AnalogIn

Zvolený pin může v daném čase pracovat pouze v jednom z těchto módů. Konfigurace prohěhne vytvořením instance třídy, parametrem konstruktoru je název pinu. Konfigurace nejnižšího pinu brány GPIOA pro digitální výstup proběhne takto:

DigitalOut pin_a0(PA_0);

Pro názvy jednotlivých pinů se používá několik různých konvencí, pro naše účely je nejvhodnější konzultace se stránkou kitu. Minimálně v počátcích našeho embedded programování si vystačíme s PA_5, ke kterému je připojedna on-board LED dioda LED1 a PC_13, ke kterému je připojeno modré uživatelské tlačítko BUTTON1.

Blinky LED

Blikající LED dioda je takový Hello World! embedded programování. Využijeme toho, že Nucleo disponuje LED diodou, která se dá programově ovládat a ukážeme si na příkladu, jak vypadá kostra programu. Abychom dosáhli správného efektu, budeme měnit logickou úroveň na pinu PA_5, ke kterému je připojena dioda a mezi změnou stavu bude program čekat ve funkci wait().

#include <mbed.h>
 
DigitalOut led(LED1);
 
int main() {
    while (1) {
        led = 1;
        wait(0.3);
        led = 0;
        wait(0.7);
    }
}

Jednoduchá třída

Mbed může využívat principy objektového programování, které známe z C++. Následující příklad ukáže, tak vytvořit jednoduchou třídu, jejíž instance bude manipulovat se stavem pinu, jehož název ja parametrem konstruktoru. K dosažení zajímavého efektu se postupně zvětšuje délka mezi změnou bliknutí.

class Blikac {
  DigitalOut pin;
public:
  Blikac (PinName p = LED1) : pin (p) {}
 
  void blikej (int n) {
    for (int i = 0; i < n; i++) {
      blik (0.1 + (0.25/n) * i);
    }
  }
 
  void blik (float d = 0.5) {
    pin = 1; wait(d); pin = 0; wait(d);
  }
};

Tuto třídu pak lze použít v hlavním programu

int main() {
  Blikac led;
 
  while(1) {
    led.blikej(5);
    led.blik();
  }
}

Komunikace po sériové lince

Standardním způsobem komunikace s mikrokontroléry je asynchronní sériová linka - USART. Duplexní komunikace probíhá po dvou vodičích (pinech), které jsou připojeny na některé z HW rozhraní1). Pro využití sériové komunikace v Mbed je třeba vytvořit instanci třídy Serial, jejíž konstruktor má jako parametry piny, po kterých probíhá komunikace. Při komunikaci přes debugovací rozhraní (USB připojení k Nucleu) lze využít předdefinovaná makra - SERIAL_TX pro odesílání a SERIAL_RX pro příjem, nebo alternatitvně USBTX a USBRX.

Serial pc(SERIAL_TX, SERIAL_RX);

Pro správnou funkci asynchronního přenosového kanálu je klíčové, aby obě strany pracovaly stejně rychle. V zásadě je možné používat libovolné rychlosti (uváděné v bitech za vteřinu, nebo se používá jednotka baud ve zkratce Bd), je ovšem vhodné používat s ohledem na doporučení ITU-T V.22 rychlosti v hodnotách násobků 1200 Bd - 1200, 2400, 4800, 9600, atd2). Za nejobvyklejší hodnotu je všeobecně považována hodnota 9600 Bd, které je použita jako defaultní i v v případě Mbed. Změnit ji lze pomocí metody Serial::baud

pc.baud(19200);

Pro komunikaci po USART implementuje Mbed podobné rozhraní, jako známe ze standardní knihovny C: znakově orientované funkce putc a getc, a funkce pro formátovaný vstup a výstup printf.

int a = 0;
while(1) {
    pc.printf("uplynulo %d vterin\n\r", a++);
    wait(1);
}

Přerušení

Nejjednoduším způsobem, jak pracovat s periferiemi mikrokontroléru, je pravidelné zjišťování jejich stavu - polling. Tento způsob práce je sice jednoduchý, ale častokrát zbytečně plýtvá časem (např. čekací funkce, která po určený čas nedělá nic, nebo čekání na data přicházející po sériové lince) a prostředky. Je proto zaveden systém přerušení, ve kterém hlavní program (nekonečná smyčka) vykonává relativně málo činností (např. spí :-) a jednotlivé periferie dávají přerušovacímu systému dohodnutým způsobem vědět, že došlo k nějaké důležité události. V tu chvíli je běh hlavního programu přerušen, vykoná se podprogram svázaný s tímto přerušením a funkce hlavního programu je obnovena. V následujícím textu budou popsány tři zdroje přerušení, se kterými budeme pracovat.

Časovač

Časovač je vnitřní periferie mikrokontroléru, která může odměřovat čas nezávisle na stavu programu. Činnost časovače probíhá tak, že po inicializaci dochází s každým hodinovým impulsem k inkrementaci hodnoty hodnoty čítače až do chvíle, než dojde k dosažení maximální hodnoty a dojde k přetečení. V tu chvíli čítač vyvolá přerušení, které je možné zpracovat podprogramem. Časovač může pracovat v kontinuálním nebo single-shot režimu. Jednou z tříd, která popisuje časovač, je Ticker. Časovač je propojen s funkcí pro obsluhu přerušení metodou Ticker::attach(), jejími argumenty jsou ukazatel na funkci a čas opakování ve vteřinách.

Následující kód bude měnit stav LED diody ve vteřinových intervalech. Na rozdíl od funkce wait() může kontrolér v době, kdy nedochází ke změně stavu, vykonávat jiný kód.

DigitalOut led(LED1);
Ticker t;
 
void obsluha() {
    led = !led;
}
 
int main() {
 
    t.attach(obsluha, 1);
 
    while(1) {
        // --
    }
}

Externí přerušení - I/O brána

GPIO umí vyvolat přerušení při změně logické úrovně na vstupním pinu. Navíc lze rozlišit, zda se jedná o změnu s vysoké úrovně na nízkou (sestupná hrana) nebo z nízké na vysokou (náběžná hrana).

Následující kód ošetřuje obě varianty změny na vstupním pinu PC13 (USER_BUTTON), navíc konfiguruje vstupní pull-up rezistor, aby byla správně definována logická hodnota na vstupním pinu v případě, že tlačítko není připojeno na žádnou úroveň. Nucleo modrým tlačítkem pin PC13 připojuje na GND, proto je třeba definovat hodnotu v rozepnutém stavu rezistorem připojeným k napájecímu napětí, navíc to znamená, že stisknutí tlačítka vytváří sestupnou hranu.

DigitalOut led(LED1);
InterruptIn button(USER_BUTTON);
 
void press () {
    led = 1;
}
 
void release () {
    led = 0;
}
 
int main() {
    button.mode(PullUp);  // definice rozepnunteho stavu - pull up resistor
    button.fall(release);
    button.rise(press);
 
    while(1) {
        // --
    }
}

Sériová komunikace

Použití funkce scanf při práci se sériovým kanálem zablokuje běh programu, což je nevýhodné zejména pokud data přicházejí v nepravidelných intervalech. Naštěstí může ovladač sériového komunikačního kanálu také vyvolat přerušení a to v případě, že se ve vstupním bufferu objeví data, která je možné číst.

Následující příklad ukazuje, jak propojit periferii s podprogramem pro obsluhu přerušení. Zajímavé je, jak se program chová: za normálních okolností je hlavní program v nekonečné smyčce, pokud přijde byte po sériové lince, zavolá se poprogram a tam čeká funkce scanf, dokud nebude ukončen přenos, znakem 0x0D (Carriage Return - Enter).

Serial pc (USBTX, USBRX);
 
void obsluha () {
  int a;
  pc.scanf("%d", &a);
  pc.printf("a = %d", a);
}
 
int main() {
    pc.attach(obsluha);
    while(1) {
        // --
    }
}

1)
Teoreticky je možné vytvořit sériové spojení z libovolných dvou pinů, např. Arduino může používat knihovnu SoftwareSerial; mikrokontroléry STM32 mají ovšem dostatek HW prostředků a není třeba se touto monžností zabývat.
2)
Maximální rychlost komunikace je ovlivněna kvalitou a délkou metalického spoje a dále také odezvou mikrokontroléru na přerušení. Obecně lze říci, že maximální používaná rychlost je 115200 Bd.
courses/b2b99ppc/tutorials/10.txt · Last modified: 2020/05/10 14:34 by viteks