Přeskočit na hlavní obsah

Ovládání registrů

V embedded programech se hardware často nastavuje přes registry. Registr je malé místo v paměti, které patří nějaké části mikrokontroléru: portu neboli GPIO, časovači, UARTu, ADC převodníku nebo třeba hodinám.

Když do registru zapíšeme číslo, není to jen obyčejné číslo. Každý bit může znamenat samostatný přepínač.

Hodnota 0x55 tedy není jen "padesát pět hexadecimálně". Je to sada osmi přepínačů:

Registr jako osm přepínačů
76543210
0x55
01010101

V hexadecimálním zápisu:

uint8_t registr = 0x55;

Bitová maska

Když chceme změnit jeden konkrétní bit, vytvoříme si masku. Maska má jedničku jen tam, kde chceme něco měnit.

Například bit 5:

Maska pro bit 5
76543210
bit 5
00100000

V C:

uint8_t maska = (1 << 5);

1 << 5 znamená: vezmi číslo 1 a posuň ho o 5 bitů doleva.

Posun jedničky na bit 5
76543210
1
00000001
1 << 5
00100000

Nastavení bitu pomocí |

Když chceme jeden bit nastavit na 1 a ostatní bity nechat tak, jak jsou, použijeme bitové OR:

registr = registr | maska;

Častěji se píše zkráceně:

registr |= maska;

Příklad: v registru je 0x55 a chceme nastavit bit 5.

Nastavení bitu pomocí OR
76543210
před 0x55
01010101
maska
00100000
po 0x75
01110101

V C:

uint8_t registr = 0x55;
registr |= (1 << 5);

Výsledek je 0x75. Změnil se jen bit 5. Ostatní bity zůstaly stejné.

Tohle je přesně důvod, proč se v embedded kódu často píše |=. Nechceme přepsat celý registr. Chceme jen zvednout jeden přepínač.

Shození bitu pomocí & a ~

Když chceme jeden bit nastavit na 0 a ostatní bity nechat být, použijeme bitové AND s obrácenou maskou.

registr &= (uint8_t)~maska;

Nejdřív maska pro bit 5:

Původní maska
76543210
maska
00100000

Pak ji operátor ~ obrátí:

Obrácená maska
76543210
~maska
11011111

Tam, kde je v obrácené masce 0, se bit shodí. Všude jinde je 1, takže původní hodnota zůstane.

Shození bitu pomocí AND
76543210
před 0x75
01110101
~maska
11011111
po 0x55
01010101

V C:

uint8_t registr = 0x75;
registr &= (uint8_t)~(1 << 5);

Testování bitu pomocí &

Když chceme zjistit, jestli je nějaký bit nastavený, použijeme bitové AND:

if ((registr & maska) != 0) {
// bit je nastaveny
}

Příklad:

Test bitu 2 pomocí AND
76543210
registr
01010101
maska
00000100
výsledek
00000100

Výsledek není nula, takže bit 2 je nastavený.

Když testujeme bit, neptáme se většinou, jestli je výsledek přesně 1. Ptáme se, jestli není nula. Maska pro bit 2 totiž vrátí nenulový výsledek s jedničkou na bitu 2, což je číslo 4, ne číslo 1.

Složené masky

Někdy chceme nastavit více bitů najednou. Masky se dají spojit pomocí bitového OR.

registr |= (1 << 1) | (1 << 4);
Spojení dvou masek
76543210
bit 1
00000010
bit 4
00010000
spolu
00010010

V ATtiny hlavičkových souborech se pro tyto masky často používají názvy. Například:

USART0.CTRLB = USART_RXEN_bm | USART_TXEN_bm;

USART_RXEN_bm je maska pro zapnutí příjmu. USART_TXEN_bm je maska pro zapnutí vysílání. Operátor | je spojí dohromady a do registru se zapíše kombinace obou voleb.

Proč se někdy používá OUTSET a OUTCLR

Některé registry na ATtiny mají pohodlnější varianty. Například porty mají často registry jako DIRSET, DIRCLR, OUTSET a OUTCLR. To nejsou obyčejné obrazy cílového registru. Jsou to speciální ovládací registry, které počítají s tím, že do nich zapíšeme masku.

Jinými slovy: do DIRSET nepíšeme, jak má celý registr DIR vypadat. Píšeme do něj jen informaci, které bity se mají nastavit. Do DIRCLR podobně píšeme masku bitů, které se mají shodit.

PORTA.DIRSET = PIN1_bm;
PORTA.DIRCLR = PIN2_bm;

To znamená:

  • DIRSET vezme jedničky v masce a tyto bity nastaví na 1,
  • DIRCLR vezme jedničky v masce a tyto bity nastaví na 0.

Nuly v masce znamenají: "těchto bitů si nevšímej". To je hlavní rozdíl proti zápisu do běžného registru.

Nemusíme tedy ručně psát:

PORTA.DIR |= PIN1_bm;
PORTA.DIR &= (uint8_t)~PIN2_bm;

Čip to udělá za nás bezpečněji. Pořád je ale důležité rozumět tomu, že uvnitř jde o stejný nápad: měníme jen vybrané bity a ostatní necháváme být.

Nepřepisujte celý registr kvůli jednomu bitu

Když chcete změnit jen jeden bit, nepište do registru celou novou hodnotu. Tento zápis je špatně:

PORTA.DIR = PIN1_bm;

Tím řekneme: "celý registr DIR bude přesně tato hodnota".

Pokud už v registru byly nastavené jiné piny jako výstupy, tímto zápisem je můžeme omylem shodit.

Bezpečnější zápis bývá:

PORTA.DIR |= PIN1_bm;

nebo na ATtiny přímo:

PORTA.DIRSET = PIN1_bm;

Tady říkáme: "nastav jen tento bit, ostatních si nevšímej".

U registrů je tohle zásadní zvyk. Jeden špatně přepsaný byte může změnit víc věcí, než jsme měli v plánu.