Bittioperaatiot C-kielessä¶
Osaamistavoitteet: Tämän materiaalin läpikäytyäsi tiedät miten bittioperaatioita tehdään C-kielellä ja miksi ne ovat tärkeitä laiteläheisessä ohjelmoinnissa.
Bittioperaatiot on meidän varsin tärkeää ymmärtää, niillä on oma merkittävä roolinsa sulautettujen ohjelmoinnissa. Bittioperaatiot johdetaan digitaalitekniikasta ja me tarvitsemme seuraavia: AND, OR, NOT, XOR ja lisäksi siirto-operaatiot (engl. shift).
Loogisista operaatioista bittioperaatiot erottaa se, että ne tehdään (binääri)luvun jokaiselle bitille erikseen, eikä esimerkiksi tarkastella koko luvun arvoa sellaisenaan.
Siirto-operaatiot¶
Siirto-operaatiot ovat C-kielessä:
- Siirto "vasemmalle" m:n bitin verran
<< m
- Siirto "oikealle" m:n bitin verran
>> m
Ja ne käyttäytyvät seuraavasti.
01100101 << 1 = 11001010 01100101 << 2 = 10010100 .. 01100101 >> 1 = 00110010 01100101 >> 2 = 00011001 ..
Kuten huomaamme, siirto-operaatio pudottaa molempiin suuntiin ''reunan yli" bittejä. Tämä johtuu siitä, että muuttujan tyyppi ei salli enempää kuin sovitun määrän bittejä. Tässä siis toteutuu aiemmin mainittu giljotiini myös.
Eli laiteläheisessä C-kielessä on hyvä huomata, ettei muuttujan lukualue saa olla myöskään liian pieni halutulle operaatiolle, muuten lopputulos voi yllättää..
Hox! Siirto vasemmalle yhden bitin verran vastaa lukualueen rajoissa luvun kertomista kahdella ja siirto oikealle yhden bitin verran vastaa luvun jakamista kahdella.
Loogiset bittioperaatiot¶
Loogiset bittioperaatiot ovat C-kielessä:
&
AND|
OR^
XOR~
NOT eli negaatio
Ja ne käyttäytyvät seuraavasti. Operaatio kohdistetaan luvun / lukujen jokaiseen bittiin erikseen.
00001111 AND: molempien operandien toisiaan vastaavat bitit ovat ykkösiä & 10101010 -------- 00001010 00001111 OR: jommankumman operadin bitti on yksi | 10101010 -------- 10101111 00001111 XOR: operadien toisiaan vastaavat bitit ovat "erit" (0 ja 1 tai 1 ja 0) ^ 10101010 -------- 10100101 ~ 00001111 NOT: käännetään ykköset nolliksi ja päinvastoin -------- 11110000
Hox! On hyvin tavallista koodissa sekoittaa &-operaattori (bittioperaatio AND) ja &&-operaattori (looginen AND).
Bittimaskit¶
Sijoitusoperaatiot ja ym. bittioperaatiot kohdistuivat kaikkiin luvun bitteihin asettaen niille jommankumman tilan. Tämä ei ole oikein kätevää, jos haluamme koskea jonkin muuttujan yksittäiseen bittiin.
muuttuja = 76; // nyt muuttujan kaikki bitit on asetettu joko nollaan tai ykköseen (76 = 01001100)
Vaihtaaksemme jonkin yksittäisen bitin tilan, joutuisimme selvittämään ensin kaikkien muiden bittien tilan ja pitämään sen ennallaan. Aika iso operaatio koodata itse, kun pitäisi testata jokaisen bitin tila erikseen.
Bittimaskilla voimme valita halutut bitit muuttujasta, joihin operaatio kohdistuu. Tämä tapahtuu merkitsemällä maskiin ne halutut bitit ykköseksi ja muut pysyvät nollina. Ts. luomme binääriluvun joka vastaa haluttua maskia.
Esimerkkejä.
Maskilla 00100001 operaatio kohdistuu 1. ja 6. bittiin. Maskilla 11000000 operaatio kohdistuu 7. ja 8. bittiin.
Bittimaski voidaan luoda joko sijoitusoperaatiolla tai bittioperaatiolla.
uint8_t maski = 0x22; // Muuttuja nimeltä maski, jossa heksalukua 0x22 vastaavat bitit on merkitty
#define MASKI 0x22 // Vakio nimeltä MASKI jossa heksalukua 0x22 vastaavat bitit on merkitty
uint8_t maski = (1 << 5) | (1 << 1) ; // binääriluku 00100010
#define MASKI ((1 << 5) | (1 << 1))
Tai-operaatio avattuna:
00100000
| 00000010
--------
00100010 = 0x22
Bittimaskin käyttäminen¶
Bittimaskeja laiteläheisessä ohjelmoinnissa käytetään ohjaamaan oheislaitetta asettamalla sen ohjausväylän bittejä haluttuun arvoon ja tulkitsemaan oheislaitteen tilaa tai sen lähettämiä signaaleja. Esimerkiksi, kirjoitusoperaatiota voisi vastata bitin tila 1 ja lukuoperaatiota tila 0. Tämä tiedoksi tässä vaiheessa, asiaan palaamme jatkossa..
Bittioperaatioilla voidaan maskia hyväksikäyttäen siis..
1. Asettaa vain halutut bitit ykkösiksi ("päälle") TAI-operaatiolla.
Oletetaan:
uint8_t ohjausrekisteri = 0x3; // 00000011
uint8_t maski = 0x10; // 00010000
Vaadittava bittioperaatio on:
ohjausrekisteri = ohjausrekisteri | maski;
Operaatio avattuna:
00000011 ohjausrekisteri ennen
| 00010000 maski
--------
00010011 ohjausrekisteri jälkeen
Eli lopputuloksena, ohjausrekisteri-muuttujasta vain maskin osoittama bitti asetettiin ykköseksi ja muut bitit jäivät siihen tilaan missä olivat. (Tämän jälkeen ohjausrekisterin muuttunut arvo pitäisi lähettää laitteelle.)
2. Asettaa vain halutut bitit nollaksi ("pois päältä") AND- ja NOT-operaatioilla.
Oletetaan:
uint8_t ohjausrekisteri = 0x3F; // 00111111
uint8_t maski = 0x12; // 00010010
Vaadittava bittioperaatio on:
ohjausrekisteri = ohjausrekisteri & ~(maski);
Operaatio avattuna:
~ 00010010 maski
--------
11101101 maskin negaatio
00111111 ohjausrekisteri ennen
& 11101101 maskin negaatio
--------
00101101 ohjausrekisteri jälkeen
Eli lopputuloksena, ohjausrekisteri-muuttujasta vain maskin osoittamat bitit asetettiin nollaksi ja muut bitit jäivät siihen tilaan missä olivat.
Koodiesimerkki¶
Koodiesimerkki reaalimaailmasta... sulautetun laitteen tietyn ledin asetus päälle ja pois, niin ettei muiden ledien tiloihin kosketa.
Määritellään numeraaliset vakiot, jotka kuvaavat ledejä vastaavia bittejä:
#define LED_POWER 0 // Eli nyt power-lediä vastaa bitti nro 1.
#define LED_MEASURE 1 // lediä vastaa bitti nro 2.
#define LED_DEBUG 4 // ...
Operaatiot (ledien tila näkyy ledit-muuttujassa:
uint8_t ledit |= (1 << LED_DEBUG); // debug-ledi päälle TAI-operaatiolla
ledit &= ~(1 << LED_DEBUG); // debug-ledi pois päältä AND- ja NOT-operaatioilla
Puretaanpas nämä hieman mystisen näköiset bittioperaatiot
uint8_t ledit |= (1 << LED_DEBUG); // debug-ledi päälle
Tarkoittaa samaa kuin:
ledit = ledit | (1 << LED_DEBUG);
Bittimaski: (1 << LED_DEBUG) = (1 << 4) = 00010000, kun korvataan vakio LED_DEBUG sen numeroarvolla
Sovitaan, että bitit joiden arvoa emme tiedä emmekä niistä välitä, merkitään x:llä.
Nyt ledit-muuttujan alkutilanne on siis xxxxxxxx
Tehdään TAI-operaatio:
ledit xxxxxxxx
maski 00010000 |
--------
xxx1xxxx
Nyt siis x:n TAI-operaatio 0:n kanssa ei muuta x:n arvoa miksikään,
mutta TAI-operaatio ykkösen kanssa pakottaa x:n ykköseksi.
Joten, lopputuloksena bitit josta emme olleet kiinnostuneita pysyivät x:nä
ja valittu bitti "pakotettiin" ykköseen.
Entäs ledin asetus pois päältä..
ledit &= ~(1 << LED_DEBUG); // debug-ledi pois päältä
Tarkoittaa samaa kuin:
ledit = ledit & ~(1 << LED_DEBUG);
Nyt taas ledit-muuttujan alkutilanne on xxxxxxxx
Bittimaski, kun korvataan vakio sen numeroarvolla (1 << 4) = 00010000
Tehdään ensin maskin negaatio, jonka tulos on 11101111
Sitten JA-operaatio negaation kanssa:
ledit xxxxxxxx
maski 11101111 &
--------
xxx0xxxx
Nyt siis x:n JA-operaatio ykkösen kanssa ei muuta x:n arvoa miksikään,
mutta JA-operaatio nollan kanssa pakottaa x:n nollaan.
Joten, lopputuloksena bitit joista emme olleet kinnostuneita, pysyivät x:nä
ja valittu bitti "pakotettiin" nollaan.
Noin, nyt osaat jo ohjata C-kielellä sulautetun laitteen tai sen oheislaitteen toimintaa!
Maskit ja rekisterit¶
Sulautetuissa järjestelmissä oheislaitteet usein viestivät CPU:n kanssa erityisten omien muistipaikkojensa, eli rekisterien, kautta. Näistä lisää tulevassa materiaalissa, mutta otetaan tässä esimerkki miten bittioperaatioilla käsitellään näiden rekisterien arvoja. Esimerkiksi ohjataan oheislaitteen toimintaa tai luetaan siltä dataa.
Alla kuvassa 16-bittisen rekisterin kuvaus, jossa on merkitty väreillä kolme bittiryhmää
x3..x0
, y7..y0
ja z3..z0
. Oheislaite itse määrittää mitä nämä eri ryhmät ja niiden bitit merkitsevät (ja se ei meitä tässä vaiheessa vielä kiinnosta). Tarkastellaan kuitenkin miten voimme bittioperaatioilla irroittaa halutut operaatiot rekisterimuuttujasta. Kuvauksessa yllä olemme merkinneet maskilla bitit
y7..y0
, jotka ovat esimerkiksi oheislaitteelta luettu (8-bittinen) sana. Sanan arvon saamme tietää, kun käytämme ensin bittioperaatiota maskin avulla ja sen jälkeen siirto-operaatiota. Haluttu lukuarvo on sitten vastaava binääriluku. Siirto-operaatio on oleellinen, koska muutoin binäärilukumme alin bitti y0 (LSB) ei vastaa pienintä kakkosen potenssia.
Lopuksi¶
Tämä jokseenkin kuiva materiaali tarjosi meille tärkeitä eväitä sulautettujen ohjelmointiin. Bittimaskit ovat yksi yleisimmin käytetyistä C-kielen keinoista ohjata oheislaitteita. Usein eri oheislaitteiden kirjastot piilottavat nämä bittioperaatiot ohjelmoijalle nätimpien funktio-kutsujen alle, mutta kirjastojen lähdekoodia tarkastellessa sieltä alta ne kyllä löytyy!
Alla operaatioiden yleinen suoritusjärjestys C-kielessä. Tämä taulukko voi auttaa bugien selvittelyssä, mutta yleisesti on parempi käyttää sulkeita tekemään koodista luettavaa ja pitämään hyönteiset poissa koodista. Erityisesti vakioiden kanssa suositaan sulkeita, josta lisää myöhemmin..
Anna palautetta
Kommentteja materiaalista?