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čů:
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:
V C:
uint8_t maska = (1 << 5);
1 << 5 znamená:
vezmi číslo 1 a posuň ho o 5 bitů doleva.
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.
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:
Pak ji operátor ~ obrátí:
Tam,
kde je v obrácené masce 0,
se bit shodí.
Všude jinde je 1,
takže původní hodnota zůstane.
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:
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);
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á:
DIRSETvezme jedničky v masce a tyto bity nastaví na1,DIRCLRvezme jedničky v masce a tyto bity nastaví na0.
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.
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.