Bits en Bytes.


TinyMega Home

Bits
Bytes
Hexadecimale notatie.
Negatieve getallen.
Grotere getallen.
Letters en cijfers.


Bits

Bits en bytes vormen de basis van de wereld van de digitale techniek. De definities zijn simpel genoeg:

Een bit is de kleinste eenheid van data. Elke bit heeft een waarde waarbij niet meer dan 2 mogelijkheden bestaan. Vandaar ook de naam bit, een samentrekking van "Binary digit". Digit staat voor 'cijfer' en binary geeft aan de er niet meer dan 2 mogelijke waarden zijn. Vergelijk het met een lichtschakelaar. Die kan 'aan' of 'uit' zijn.

Een bit kan bijvoorbeeld worden opgeslagen in een flip-flop schakeling. De schakeling onthoud de bit, en de waarde kan worden gemeten aan de uitgang van de flip-flop. Die uitgang kan 'hoog' zijn of 'laag'. Afhankelijk van de gebruikte techniek wordt daarbij een kantelpunt gehanteerd. Is de spanning lager dan het kantelpunt, dan is de waarde laag, is de spanning hoger dan is de waarde 'hoog'. Bij logische schakelingen uit de 74HCxx reeks kun je -bij een voedingsspanning van 5Volt- het kantelpunt op 2.5 Volt stellen.

Dus een bit kan 'hoog' en 'laag' zijn. En die toestanden kunnen we ook een andere naam geven. Bijvoorbeeld '1' en '0', of 'wel' en 'niet', of 'aan' en 'uit'. Het is maar net wat je met de informatie wilt doen.

Als je wilt gaan rekenen bijvoorbeeld dan is het handig om het bit een numerieke waarde te geven. Dus 0 en 1. Dan kunnen we meteen gaan tellen:
Als we beginnen staat het bit uit: Waarde = 0
Dan tellen we er 1 bij op : Waarde wordt 1

En dan zijn we uitgeteld. Want alle mogelijke waarden zijn opgebruikt. Als je toch verder wilt tellen, dan heb je meer bits nodig. Als je tot 4 wilt tellen dan kan dat met 2 bits, en we spreken de volgende spelregels af:

Dus als we vanaf 00 telkens 01 optellen dan krijgen we:

00
01
10
11
00Zou eigenlijk 100 moeten zijn, maar we hebben maar 2 bits dus de 1 valt eraf.
01
10
11
00enzovoorts.

Dus onze 2-bits teller blijft steeds dezelfde 4 verschillende waarden aflopen.


Bytes

0000 0000 0
0000 0001 1
0000 0010 2
0000 0011 3
0000 0100 4
.... .... ...
1111 1101 253
1111 1110 254
1111 1111 255
0000 0000 0

Nu is twee bits wel erg weinig om mee te tellen. Dus meestal gebruiken we meerdere bits tegelijk. Een groep van 8 bits wordt een byte genoemd. Dat begint al bruikbaar te worden. Als elke bit 2 verschillende waarden kan hebben, dan zijn er met 8 bits al 2 tot de macht 8 = 256 verschillende mogelijkheden. Zoals u ziet kunnen we nu veel verder doortellen. Maar ook hier zijn alle bits opgebruikt, en rolt alles door naar 0. Ook ziet u dat we de bits nu telkens in groepjes van 4 laten zien. Dat leest wat gemakkelijker.


Hexadecimale notatie.

BitsWaardeHex Digit
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F

Om de data iets compacter op the schrijven wordt vaak de Hexadecimale notatie gebruikt. Dit is een 16-tallig stelsel. We nemen telkens groepjes van 4 bits. Dan krijgen we dus 16 mogelijke waardes, en elk van die waardes krijgt een cijfer. Voor de eerste 10 combinaties gebruiken we natuurlijk gewoon de cijfers '0' t/m '9' zoals we die kennen uit het decimale stelsel, en vervolgens nummeren we door met 'A' t/m 'F'.

Als we nu grotere binaire getallen tegenkomen, dan kunnen we de bits telkens onderverdelen in groepjes van 4 (nibble), en elk groepje omrekenen volgens de tabel. Dan is het alleen niet duidelijk of we een decimale waarde bedoelen of een Hexadecimale waarde. Om misverstanden te voorkomen kun je dan beter aangeven welk getalsysteem wordt bedoeld, door het getal te laten vooafgaan door '0x'. Een 16-bits getal met alle bits of 0 wordt dan 0x0000, alle bits hoog wordt 0xFFFF.

Deze notatie wordt ook door elke compiler ondersteund. Als u in een programma het getal 15 gebruikt, dan wordt dat gelezen als decimal getal, en wordt omgezet naar 0b00001111. Om het getal als Hex vast te leggen dan moet je 0xF of 0x0F gebruiken. Voorloop-nullen mag je weglaten. Die worden zonodig door de compiler vanzelf toegevoegd.

De binaire notatie (aangegeven door 0b) wordt in de praktijk minder gebruikt. Die notatie wordt ook niet door alle compilers ondersteund. Persoonlijk vind ik het wel een fijne notatie als je op bit-niveau moet programmeren. Gelukkig heeft onze avr-gcc compiler daar geen problemen mee.



Negatieve getallen.

Dus een byte kan elke waarde krijgen tussen 0 en 255. Er zijn geen cijfers achter de komma. We kunnen dus alleen gehele getallen opslaan. En er is ook geen min-teken. Dus negatieve getallen kunnen we ook niet opslaan. En dat is soms wel vervelend, dan willen we dus wel negatieve getallen opslaan. Dus doen we net alsof. Dan spreken we af dat we een byte behandelen alsof het een 'signed value' is, dwz een getal met teken dat dus ook negatief kan worden. Maar dat verandert niets aan de byte zelf. Die blijft gewoon 8 bits groot en daarmee blijft het aantal mogelijke waarden ook 256. Maar we kunnen wel afspreken dat we het getal als positief beschouwen als het in de range 0b00000000 t/m 0b01111111 ligt, en negatief als het in de range 0b1000000 t/m 0b11111111. Dat is handig, want dan kun je aan het hoogste bit zien of het getal positief of negatief is. Als we dan ook afspreken dat 0b11111111 gelijk is aan -1 dan krijgen we allerlei handige effecten.

Deze afspraak is zo handig en zo algemeen dat die is ingebouwd in de instruktie-set van elke processor. De notatie heeft ook een naam : "two's complement".

TypeBereik positiefBereik negatief
TypeBinairHexDecimaalBinairHexDecimaal
unsigned0b00000000 t/m 0b111111110x00 t/m 0xFF0 - 255------
signed 0b00000000 t/m 0b011111110x00 t/m 0x7F0 - 1270b10000000 t/m 0b111111110x80 t/m 0xFF-128 t/m -1

Let wel, als je gaat tellen in een signed byte, vanaf 0 omhoog, dan krijg je exact de zelfde bit-combinaties als wanneer je gaat tellen met een unsigned byte. De bits lopen gewoon van 0x00 - 0x01 etc .. 0xFF - 0x00 - 0x01 etc. Maar het overflow punt verschuift wel. Bij een unsigned byte springt de interpretatie van 255 naar 0, en ontstaat dus een overflow vanaf 255. Bij signed bytes verandert de interpretatie na 127, want dan springt het getal ineens naar -128. Maar dan is er geen probleem rond 0. Daar tel je gewoon door (-3, -2, -1, 0, 1, 2 etc).



Grotere getallen.

Soms is het bereik van een enkele byte voldoende. Soms ook niet. Als je grotere getallen nodig hebt, dan kun je een aantal bytes aan elkaar koppelen. Dan krijg je meer bits en dan kun je dus grotere getallen opslaan. Meestal werk je in een taal als 'C' en dan heb je de beschikking over verschillende typen van getallen. Voor de processor maakt dat allemaal niet uit. Maar de compiler weet het type van de getallen en genereert dus ook de juiste instrukties die rekening houden met het type van de getallen. Het aantal bytes per type getal is afhankelijk van welke compiler je gebruikt. Elke compiler-fabrikant maakt daarin zijn eigen keuzes. De taal 'C' geeft expres geen definities zodat de compiler een set types kan implementeren die het beste resultaat geeft op de machine die het programma moet verwerken.

Dit zijn de types zoals die worden ondersteund door AVR-GCC, die ook in ATMEL STUDIO wordt gebruikt:

Typenr bitsBereik MinBereik MaxComment
unsigned char8+0+255
signed char8-128+127
char8????Kan signed of unsigned zijn, afhankelijk van compiler opties.
(signed) int 16-32768+32767Is signed by default.
unsigned int 160+65535.
(signed) long32-2147483648+2147483647Is signed by default.
unsigned long320+4294967295


Letters en Cijfers.

Dus integers, daar kun je mee rekenen. Maar soms wil je ook met tekst werken. En dan wil je dus de afzonderlijke letters, cijfers en leestekens vastleggen. De processor werkt nog altijd in bytes, en in elke byte hebben we 256 verschillende waarden. Dus als we tekst willen vastleggen dan moeten we afspreken hoe we de byte-waarde vertalen naar een letter. Feitelijk kun je zelf een vertaling kiezen, maar het is veel handiger als iedereen dezelfde vertaling gebruikt. Dat blijkt in de praktijk nog niet mee te vallen, omdat lang niet iedereen dezelfde set letters gebruikt. Denk aan Grieken, Russen, Israeliers, Arabieren, Chinezen enz). Maar de ASCII set is toch wel zo algemeen bekend dat die vrijwel overal gebruikt kan worden. ASCII is de afkorting van "American Standard Character-set for Information Interchange". Het definiëert alle 26 letters, (hoofd-letters en kleine letters apart, de cijfers en aan aantal leestekens. Dus daarmee kun je tekst vastleggen. Het gebruikt alleen code's tussen 0x00 en 0x7F, dus je hebt genoeg aan 7 bits per letter, maar in de praktijk gebruiken we vrijwel altijd 1 volle byte per letter.

De volledige ASCII tabel ziet er als volgt uit:

0x00 0x100x200x300x400x500x600x70
0x00NULL DLE Spatie0@P`p
0x01SOH DC1 !1AQaq
0x02STX DC2 "2BRbr
0x03ETX DC3 #3CScs
0x04EOT DC4 $4DTdt
0x05ENQ IDLE%5EUeu
0x06ACK NAK &6FVfv
0x07BELL ETB'7GWgw
0x08BackSpaceCANCEL(8HXhx
0x09TAB EOM )9IYiy
0x0A LF SUB *:JZjz
0x0BVTAB ESC+;K[k{
0x0CFormFeed FS ,<L\l|
0x0D CR GS -=M]m}
0x0EShift OutRS .>N^n~
0x0FShift In US /?O_oDEL

De bovenstaande ASCII table zit gelukkig ingebakken in de AVR-GCC compiler (en in de meeste andere compilers). Als je bijvoorbeeld in je programma een letter tussen enkele quotes zet, dan wordt die door de compiler vertaald naar de bijbehorende ASCII code. Dwz dat de compiler de instrukties genereert die het bedoelde effect opleveren.

   char c = 'A';          // De variable 'c' krijgt de waarde uit de ASCII table voor de letter 'A', dus 0x41.
                          // - enkele quotes voor een enkele letter.

   char Naam[] = "John";  // Dubbele quotes voor een string met meerdere letters.
                          // - Altijd inclusief een NULL om einde-Text te markeren, hier dus 5 bytes.
                          // - 0x4A 0x6F 0x68 0x6E 0x00