Přeskočit na hlavní obsah

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_bm je bitová maska pro pin PA1,
  • PIN2_bm je bitová maska pro pin PA2,
  • DIRSET nastaví vybraný pin jako výstup,
  • DIRCLR nastaví vybraný pin jako vstup.

PA1 je tedy výstup. PA2 je vstup.

Proč DIRSET a DIRCLR

Mohli bychom si představit registr DIR takhle:

Registr DIR pro port A
PA7PA6PA5PA4PA3PA2PA1PA0
DIR
00000000

Když chceme nastavit PA1 jako výstup, chceme změnit jen bit 1:

Nastavení PA1 jako výstupu
PA7PA6PA5PA4PA3PA2PA1PA0
před
00000000
PIN1_bm
00000010
po
00000010

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:

Zapnutí výstupu PB2
PB7PB6PB5PB4PB3PB2PB1PB0
před
01000011
PIN2_bm
00000100
po |=
01000111

Bit 2 se zvedl. Ostatní bity zůstaly stejné.

Shození stejného bitu:

Vypnutí výstupu PB2
PB7PB6PB5PB4PB3PB2PB1PB0
před
01000111
~PIN2_bm
11111011
po &=
01000011

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.

Čtení stavu PA2
PA7PA6PA5PA4PA3PA2PA1PA0
PORTA.IN
10100100
PIN2_bm
00000100
výsledek
00000100

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álostvýznam
náběžná hranasignál se změní z 0 na 1
sestupná hranasigná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.

Tlačítko není dokonale čistý signál

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.