Table of Contents

Registers

One of the toughest things for beginners to understand in a microcontroller is typically a register. When dealing with microcontrollers, it is impossible to get by without knowing what this device is. This book is in no way different, as the reader is also expected to familiarize him/herself with the concept of a register and therefore the following text will try to explain it in simple enough terms so that even a beginner can grasp the idea of a register.

Essence

Tape player's buttons

A register is like a panel of buttons on a home appliance. It has switches, which can be turned on or off. One of the best examples is a simple tape player. For those, who don't remember, the tape player has (had) 6 buttons, left to right:

Each button does something, but only when it is used correctly. For example, the stop button does nothing unless a tape is playing - only then will it do something visible and stop the playback. Forward and rewind buttons, on the other hand, can be pressed at any time, because the tape can be wound in both directions, no matter if it is playing or stopped. The recording begins only when the play and record buttons are pressed down simultaneously. Some may have tried to press down several or all buttons at once - in this case, the tape player might have done something unexpected or break altogether.

Microcontroller registers behave like buttons on a tape player - each button does something, when used correctly. By pressing the wrong buttons, a microcontroller usually won't break, but it won't work either. In reality, there are no buttons in the registers, instead there are a whole lot of transistors, which turn the electricity on and off. Simpler microcontrollers have 8 transistor-based switches in a single register. A register can be thought of as an 8-bit number, where every bit is marked by the state of one of those switches. For example, a bit value of 1 can mean that the switch is on and 0 that the switch is off.

Register's “buttons” and bit values

Since the state of a register's switches can easily be displayed as a number, a register can be compared to a memory, which can hold data in the size of one number. By this comparison, we see that registers actually are memory slots. The difference between a register and a memory slot is that a memory slot only stores the information, but in a register this information actually controls something. For example, if a binary value of 01100001 is written to a register, then three imaginary buttons are pressed down and something happens.

On a tape player it is possible to press each button separately, but in a register it is more difficult to change the value of one “switch” or bit. Typically it is necessary to change the entire content of the register. Before moving on to bit changing, one should know that there are a lot of registers in a microcontroller. Some parts of the microcontroller use tens of registers to control them. The variety of registers means that there has to be a way to distinguish between different registers and that is done by naming the registers. One register, for example, is called PORTB. Actually, these names are just to make things easier for the developer and each name corresponds to a numeric address.

Usage

To write to a register or read a value from it, it has to be addressed as a variable in C. The following example demonstrates writing a binary value to an imaginary register REG and then reading it to variable reg. Binary values are distinguished by 0b (leading zero), so that the compiler understands the numeric system.

  REG = 0b01100001;
  unsigned char reg = REG;

There is nothing difficult in writing and reading register values, but it gets a little tricky if only a single bit needs to be changed. To change bits, one needs to know how to do binary math and use different numeric systems. It isn't forbidden to deal only with binary numbers, but they can be a bit troublesome, because binary numbers are quite long, and this is why most people use shorter hexadecimal numbers.

Hexadecimal numbers

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 multiple bits.

Negation, logical multiplication, logical addition and exclusive disjunction

 

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.

Setting a Single Bit High

Setting a single bit high

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

Setting a Single Bit Low

Setting a single bit low

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

 

Inverting a Single Bit

Inverting a single bit

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

Inverting the Whole Register

Inverting all bits

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

Reading the Value of a Single Bit

Reading the value of a bit

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

 

Shifting a Bit

Many programming languages actually have a few additional bitwise operations, which make it easier for programmers. These are bit shifting operations that shift bits left or right in a binary number. The main advantage of shift operations in dealing with registers is their ability to convert bit ranks to bitmasks and vice versa.

Shift left

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

The shift-right operation works similarly to the 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 previous example showed how to read the value of a single bit. Let's suppose the bit to be 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.

AVR Registers

To actually do anything 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 of the microcontroller. The datasheet also describes the registers. The following will help understand the register descriptions in AVR datasheets.

One of AVRs registers from its datasheet

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. Just as each register has a name, each bit in the register also has 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