This is an old revision of the document!
Eins der schwersten Dinger die ein Anfänger verstehen kann im Bereich des Microcontrollers ist normalerweise das Register. Wenn man mit Microcontroller arbeitet ist es unmöglich darum zu kommen ohne zu wissen was Register sind. Dieses Buch ist da nicht anders, daher wird vom Leser erwartet sich mit dem Konzept der Register vertraut zu machen, der folgende Text versucht das mit möglichst einfachen Ausdrücken zu beschreiben, damit selbst ein Anfänger eine Idee davon bekommen kann was ein Register ist.
Ein Register ist wie ein Feld von Knöpfen an einem Haushaltsgerät. Es hat Schalter welche An und Aus geschaltet werden könne. Als bestes Beispiel ist der Videorekorder. Für die, die sich nicht mehr erinnern, der Rekorder hat/hatte sechs Knöpfe, von links nach rechts:
Jeder Knopf macht etwas bestimmtes, aber nur wenn er korrekt benutzt wird. Zum Beispiel der Stopknopf macht nichts, außer die Kassette spielt grade – nur dann wird der Knopf etwas bewirken und stoppt die Wiedergabe. Vor- und Zurückspul-Knöpfe, können jederzeit gedrückt werden, weil das Band in beide Richtungen gedreht werden kann, egal ob das Gerät grade abspielt oder gestoppt ist. Manche haben vielleicht versucht mehrere Knöpfe auf einmal, vielleicht sogar alle auf einmal zu drücken – in diesem Fall hat der Rekorder vielleicht was unvorhergesehendes getan, oder ist sogar kaputt gegangen.
Microcontroller Register verhalten sich wie Knöpfe an einem Videorekorder – jeder Knopf mach etwas Bestimmtes, wenn er richtig benutzt wird. Drückt man den falschen Knopf , wird ein Microcontroller wahrscheinlich nicht kaputt gehen, aber er wird definitiv nicht funktionieren. In Wirklichkeit gibt es keine Knöpfe in den Register, sondern eine Menge an Transistoren, welche den Strom an- und ausstellen. Einfache Microcontroller haben acht Transistor-basierende Schalter in einem Register. Ein Register kann als 8-Bit Nummer gesehen werden, wo jeder Bit durch den Status eines der acht Schalter repräsentiert wird. Zum Beispiel ein Bit mit dem Wert 1 kann bedeuten der Schalter ist an und 0, dass der Schalter aus ist.
Da der Status der Registerschalter einfach als Nummer dargestellt werden kann und vice versa, kann ein Register mit einem Speicher verglichen werden, der Daten in der Größe einer Nummer halten kann. Mit diesem Vergleich wird deutlich, dass Register eigentlich SpeicherSlots sind. Der Unterschied zwischen Register und Speicher-Slot ist der, dass im Speicher-Slot nur etwas gespeichert wird, aber im Register die Information etwas kontrolliert. Zum Beispiel der Binärwert 01100001 wird in ein Register geschrieben dann werden drei imaginäre Knöpfe gedrückt und etwas Bestimmtes passiert.
An einem Videorekorder ist es möglich jeden Knopf einzeln zu drücken, aber in einem Register ist es schwieriger den Wert eines „Schalters“ oder Bit zu ändern. Normalweise ist es notwendig den gesamten Inhalt des Register zu ändern. Bevor wir zum Ändern des Bit kommen, sollte man wissen, dass es im Microcontroller eine Menge Register gibt. Einige Teile eines Microcontroller benötigen mehr als zehn Register um kontrolliert zu werden. Die Vielfalt der Register bedeutet, dass es einen Weg geben muss, zwischen verschiedenen Registern zu unterscheiden. Das wird gemacht, in dem Man den Registern Namen gibt. Ein Register heißt z.B. PORTB. Eigentlich machen diese Namen es nur einfacherer für die Entwickler, jeder Name hat eine eigene numerische Addresse.
Um auf ein Register zu schreiben, oder den Wert abzulesen, muss er mit einer Variable in C adressiert sein. Das folgende Beispiel demonstriert, wie man einen Binären Wert auf einen imaginären Register REG schreibt und dann die Variable reg ausliest. Binäre Werte werden an einem 0b(leading zero) erkannt, so dass der Compiler das numerische System erkennt
REG = 0b01100001; unsigned char reg = REG;
Es gibt nichts Schwieriges im Schreiben und Lesen von Register Werten, aber es wird ein wenig kniffelig, wenn man nur ein Bit ändern will. Um Bits zu ändern, muss man wissen wie binäre Rechnen funktioniert und unterschiedliche numerische System nutzt. Es ist zwar nicht verboten nur mit binären Nummern zu arbeiten, aber es kann sehr umständlich werden, weil binäre Nummern doch sehr lang werden, und das ist der Grund warum viele Menschen die kürzeren Hexadezimalzahlen nutzen.
In hexadecimal, the numbers are not only 0 and 1 as in binary or 0 to 9 as in decimal, but instead 0 to F. A hexadecimal number consists of four bits. The table on the right shows the binary numbers and their hexadecimal counterparts. Binary numbers are converted to hexadecimal by reading bits four at a time, starting from the lowest rank. Ranks are read from right to left and their numbers start from 0. For example, the lowest ranked (rank 0) bit is 0 and the highest (rank 3) is 1. In the previous example, the register's binary value is 01100001, which is 61 in hexadecimal and is written as 0x61 (leading zero) in C.
To change single bits in a number (register, variable or anywhere else for that matter) it is necessary to use binary operations. Binary operation is an operation between two binary numbers, where each bit of the numbers is subject to its own logical operation. Typically a microcontroller supports four binary operations, each having several names. The following section describes the logical operation behind each of these four binary operations with a single bit or multpile bits.
This is all one needs to know to change single bits. The theory alone is probably not enough, though, and that is why there are some typical examples with registers in the next few paragraphs.
To set one or more bits in a register high (1) a logical addition operation is needed. One of the operands of the operation must be the register and the other a binary number, where the only high bit is the one that needs to be set high in the register. This binary number is called a bitmask. Below is the C code for the operation shown on the right:
// Let's suppose REG = 0x0F REG = REG | 0x11; // First method REG |= 0x11; // Second method // Now REG = 0x1F
To set one or more bits in a register low (0) a logical multiplication operation is needed. One operand of the operation must be the register and the other a bitmask, in which the only low bit is the one that needs to be set low in the register. Below is the C code for the operation shown on the right:
// Let's suppose REG = 0x0F REG = REG & 0xFE; // First method REG &= 0xFE; // Second method // Now REG = 0x0E
To invert one or more bits in a register an exclusive disjunction operation is required. One of the operands of the operation must be the register and the other a bitmask, where the only high bit is the one that needs to be inverted in the register. Below is the C code for the operation shown on the right:
// Let's suppose REG = 0x0F REG = REG ^ 0x11; // First method REG ^= 0x11; // Second method (use only one per inversion) // Now REG = 0x1E
To invert all bits in a register a negation operation is used. This operation is unary, which means it has only one operand. Below is the C code for the operation shown on the right:
// Let's suppose REG = 0x0F REG = ~REG; // Now REG = 0xF0
To read one or more bits from a register the same operation is required as was used for setting a bit low - logical multiplication. One of the operands of the operation must be the register and the other a bitmask, where the only high bit is the one that needs to be read from the register. Below is the C code for the operation shown on the right:
// Let's suppose REG = 0x0F unsigned char x = REG & 0x01; // Now x = 0x01
Many programming languages actually have a few additional bitwise operations, which make it easier for the programmers. These are bit shifting operations that shift bits left or right in a binary number. The main value of shift operations in dealing with registers is their ability to convert bit ranks to bitmasks and vice versa.
The image on the right shows a shift left operation. Although bit shifting is not a logical operation and has no corresponding symbol, in C it is marked as “«”. Shift left is used to transform a bit rank to a bitmask. For example, to get the mask for the 6th bit (NB! rank 5), number 1 has to be shifted left 5 times. The example operation looks like this in C:
REG = 0x01 << 5; // Now REG = 0x20
Shift right operation works similarly to shift left operation. It is marked as “»” in C. Right shift is used to get the logical value of a bit from a bitmask. A leading example showed how to read the value of a single bit. Let's suppose the bit to read is not of the lowest rank, but for example of rank 5. In this case, the result would be either 0x20 or 0x00, but sometimes a result of 1 or 0 is needed and that is when the right shift comes to the rescue. The example operation on the right looks like this in C:
// Let's suppose REG = 0x20 unsigned char x = REG >> 5; // Now x = 0x01 (or simply 1)
If a bit is shifted right from the lowest rank or left from the highest rank by the bit shifting operation, it disappears. Some programming languages also have rotating bit shift operations, where the bit doesn't disappear, but moves from the lowest rank to the highest or vice versa. C doesn't have that kind of bit shift operations, but they can be written by the programmer if needed.
All bit operations work with not only registers, but with variables and constants as well. The latter can of course only be used as operands and not the result.
To do anything actual with the microcontroller's registers, one needs to know how to use that particular microcontroller. Each microcontroller comes with one or several datasheets, which describe the whole structure and functionality or the microcontroller. The datasheet also describes the registers. The following will help understand the register descriptions in AVR datasheets.
The image shows ATmega128 microcontroller's UCSRnA register, which stands for “USART Control and Status Register A”. This register is used to configure AVR's USART module and read its states. All AVR register names are written in capital letters, but as the reader might notice, the register name contains also a lower case n. A lower n is used to mark some module's index. Since ATmega128 has 2 almost identical USART modules, they are not described twice, but only once and the n must be read as 0 or 1 by the user. Therefore ATmega128 has registers UCSR0A and UCSR1A.
The content of the register is marked by an 8-slot box with a bold line around it. Each slot marks one bit. Bit ranks are marked above the box - increasing from right to left. Since AVR is an 8-bit microcontroller, most of the registers are 8-bit as well. There are some exceptions, a few registers are 16-bit, but they actually consist of two 8-bit registers. Like each register has a name, each bit in the register has also a name - just like the buttons on a tape player. Each bit is described in the datasheet. Bit names are abbreviations as well and the lower n must be substituted with the module's index, just like with register names. Some registers don't use all 8 bits, in this case the bit's slot is marked with a hyphen.
Below the register's bits are two lines, which state whether the bit is readable (R), writable (W) or both (R/W). For example, the status bits can't be overwritten and even if it's attempted in the program, the bit will remain unchanged. If the bit is marked as writable, reading it will always result in one specific value stated in the datasheet. The second line specifies the default value of the bit, which it has after the reset of the microcontroller.
While AVR register names point to an actual memory slot address, the bit names hold the rank number of the corresponding bit. Therefore it is necessary to transform the names to bitmasks using a shift operation, in order to manipulate with bits in a register. The following code contains a few example lines for using the USART 0 module's register.
// Set TXC0 bit high UCSR0A |= (1 << TXC0); // Set U2X0 bit low UCSR0A &= ~(1 << U2X0); // Read the value of UDRE0 bit(mask) unsigned char u = (UCSR0A & (1 << UDRE0)); // At this point u value is either 0 or 32, // which enables using it in a logical operation if (u) { // Invert MPCM0 bit UCSR0A ^= (1 << MPCM0); } // Sometimes it is necessary to acquire a specific 0 or 1 value, // so the read bit needs to be shifted right u >>= UDRE0; // Now the value of u is either 0 or 1