GPIO
GPIO znamená general-purpose input/output.
Česky:
univerzální vstupně-výstupní pin.
Je to pin mikrokontroléru, který může program používat třeba jako:
- výstup pro LED,
- vstup pro tlačítko,
- ovládání relé,
- čtení jednoduchého digitálního signálu,
- zapnutí nebo vypnutí části obvodu.
GPIO je často první místo, kde člověk narazí na registry. V Arduinu se píše:
pinMode(13, OUTPUT);
digitalWrite(13, HIGH);
Na nižší úrovni ale program nastavuje bity v registrech portu.
Směr pinu
Pin musí mít směr. Buď je vstup, nebo výstup.
Na ATtiny se směr portu nastavuje přes registr DIR.
Bit 1 znamená výstup.
Bit 0 znamená vstup.
Při nastavování pinů pro UART může kód vypadat například takto:
PORTA.OUTSET = PIN1_bm;
PORTA.DIRSET = PIN1_bm;
PORTA.DIRCLR = PIN2_bm;
Tady se nastavují piny pro UART:
PIN1_bmje bitová maska pro pinPA1,PIN2_bmje bitová maska pro pinPA2,DIRSETnastaví vybraný pin jako výstup,DIRCLRnastaví vybraný pin jako vstup.
PA1 je tedy výstup.
PA2 je vstup.
Proč DIRSET a DIRCLR
Mohli bychom si představit registr DIR takhle:
Když chceme nastavit PA1 jako výstup,
chceme změnit jen bit 1:
Na obecném registru by se to psalo:
PORTA.DIR |= PIN1_bm;
Na ATtiny ale existuje pohodlnější registr:
PORTA.DIRSET = PIN1_bm;
Ten říká: "nastav jedničku tam, kde má maska jedničku, ostatních bitů si nevšímej".
Když chceme PA2 jako vstup,
shazujeme bit 2:
PORTA.DIRCLR = PIN2_bm;
Obecně by to odpovídalo zápisu:
PORTA.DIR &= (uint8_t)~PIN2_bm;
Bitové operace |=, &= a ~ jsou podrobně vysvětlené v kapitole
Ovládání registrů.
Výstupní hodnota
Když je pin výstup,
program může určit,
jestli na něm bude logická 1 nebo 0.
Na ATtiny se k tomu používá třeba OUT, OUTSET a OUTCLR.
PORTA.OUTSET = PIN1_bm;
Tento řádek nastaví PA1 na logickou 1.
Často se to dělá ještě před zapnutím výstupu,
aby výstupní pin nezačal krátce ve špatném stavu.
Pro LED může být v kódu připravený třeba virtuální port:
#define SENSORS_IR_LED_PORT VPORTB
#define SENSORS_IR_LED_PIN PIN2_bm
A zapínání/vypínání LED pak může vypadat takto:
if (enabled) {
SENSORS_IR_LED_PORT.OUT |= SENSORS_IR_LED_PIN;
} else {
SENSORS_IR_LED_PORT.OUT &= (uint8_t)~SENSORS_IR_LED_PIN;
}
Když je enabled pravda,
bit se nastaví pomocí |=.
Když je enabled nepravda,
bit se shodí pomocí &= ~.
Názorně pro pin PB2:
Bit 2 se zvedl. Ostatní bity zůstaly stejné.
Shození stejného bitu:
Bit 2 spadl na nulu. Ostatní bity se nezměnily.
Vstupní hodnota
Když je pin vstup, program čte jeho stav. Obecně to vypadá takto:
if ((PORTA.IN & PIN2_bm) != 0) {
// na pinu PA2 je logicka 1
}
Použije se &,
protože nechceme číst celý port jako jedno číslo.
Chceme se zeptat jen na jeden bit.
Výsledek není nula, takže bit 2 je nastavený.
Přerušení na vstupu
Vstupní pin nemusíme jen pořád dokola kontrolovat v hlavní smyčce. U mnoha mikrokontrolérů jde nastavit, aby změna na pinu vyvolala přerušení. To se hodí třeba pro tlačítko, signál z čidla, probouzení ze spánku nebo rychlou reakci na hranu signálu.
Typicky se nastavují dvě věci:
- který pin nebo skupina pinů má přerušení vyvolat,
- na jakou událost má reagovat.
Událost může být například:
| událost | význam |
|---|---|
| náběžná hrana | signál se změní z 0 na 1 |
| sestupná hrana | signál se změní z 1 na 0 |
| změna úrovně | signál se změnil libovolným směrem |
| nízká úroveň | pin je v logické 0 |
Zjednodušeně může nastavení vypadat takto:
PORTA.DIRCLR = PIN2_bm;
PORTA.PIN2CTRL = PORT_ISC_RISING_gc;
První řádek nastaví PA2 jako vstup.
Druhý řádek nastaví,
že pin má reagovat na náběžnou hranu.
Konkrétní názvy registrů a voleb se liší podle čipu,
takže se vždycky kontrolují v manuálu.
Mechanické tlačítko při stisku často krátce zakmitá. Program pak může místo jednoho stisku vidět několik rychlých změn. Tomu se říká zákmit nebo debounce.
Přerušení tedy samo o sobě neznamená, že máte hotové spolehlivé čtení tlačítka. Často je potřeba přidat časovou filtraci v programu nebo vhodné zapojení v hardwaru.
V obsluze přerušení by měla být jen krátká práce. Třeba nastavit příznak, že se pin změnil, a zbytek vyřešit později v hlavní smyčce:
ISR(PORTA_PORT_vect)
{
tlacitko_zmeneno = true;
}
Delší vysvětlení pravidel pro ISR je v kapitole Přerušení.
Pin není vždy jen GPIO
Jeden fyzický pin může mít více funkcí. Může být GPIO, ale také UART, ADC vstup, časovačový výstup nebo třeba UPDI.
Proto se v kódu někdy objevuje i PORTMUX.
Například:
PORTMUX.USARTROUTEA =
(uint8_t)((PORTMUX.USARTROUTEA & (uint8_t)~PORTMUX_USART0_gm) |
PORTMUX_USART0_ALT1_gc);
Tento zápis přepíná,
kudy bude vyvedený USART0.
Nejdřív se smažou staré bity skupiny PORTMUX_USART0_gm,
potom se nastaví nová volba PORTMUX_USART0_ALT1_gc.
Je to typická práce s registrem, kde nechceme poškodit ostatní nastavení. Proto se nejdřív vyčistí jen vybraná skupina bitů a pak se do ní vloží nová hodnota.