Manipulating Registers in AVR Microcontrollers

This guide aims to explain what registers are, and how to work with them. It applies to miniMO and the Arduino IDE but also to microcontrollers in general, and will be most useful to you if you can understand basic programs and would like to modify the setup of the timers or other peripherals within the microcontroller, but can’t make clear sense of lines like ADMUX |= (1 << ADLAR);

UNDERSTANDING REGISTERS

A bit is a unit of information. It has a value of 1, or 0

  • One bit is enough to turn a device ON (1), or OFF (0), if we use it as a switch
  • A row of bits can be thought of as a row of switches, e.g. number 101, understood this way, is not a hundred and one, but a row of three switches where the middle switch is OFF
  • If we have several related switches, it is often useful to group them together. A byte is the smallest such group used by computers
    • A byte can be thought of as a row of eight related switches in a fuse box
      • The computer’s memory can be though of as a giant fuse box

A register is a location in a computer’s memory

  • A register has an address
  • A register has a name
  • A register contains a group of bits
  • Registers can have different sizes depending on the computer (8-bit, 16-bit, etc)
  • Some registers hold information that is read-only, while other registers allow writing to them
  • Registers have many uses, and the bits they contain take different meanings depending on their purpose
    • 101 could mean three switches, where the switch in the middle is OFF
    • 101 could also mean the decimal number 5

If we wanted to organize the light switches for a building, we could group them in registers. A Register Summary could thus look like this:

 REF NAME BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0 Page
6 BULBS    Data Register page 10
5 APT1 ROOM2 ROOM1 ROOM0 BATHR KITCHEN LIVINGR HALL
4 APT0 ROOM2 ROOM0 BATHR KITCHEN LIVINGR HALL
3 OFFICE0B ROOM4 ROOM3 ROOM2 ROOM1 ROOM0 MEETR1 MEETR0
2 OFFICE0A SERVERS BATHR1 BATHR0 KITCHEN MAIN HALL
1 Ground LIFTS STAIRS RECEPTION
0 Basement ALL

Notice how Office 0 uses two registers, OFFICE0A and OFFICE0B. Also, BULBS doesn’t hold information on the state of any lights; page 10 of the building’s maintenance manual would tell you that this register is read-only and stores the number of bulbs originally installed in the building.

With this summary at hand, we see that to turn OFF the kitchen lights in Apartment 1, we set BIT2 in the register APT1 to 0; to turn ON the server’s room lights in the office, we set BIT5 in OFFICE0A to 1. If we want to know how many bulbs were installed in this building, we look at BULBS, which (for example) reads 1110101 -that’s 117 bulbs.

Let’s take a look at a couple lines of an AVR ATtiny85’s Register Summary (page 200 of the Datasheet):

Address NAME BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0 Page
0x04 ADCL   ADC Data Register Low Byte page 137
0x03 ADCSRB BIN ACME IPR ADTS2 ADTS1 ADTS0 pages 120,137

The one noticeable difference between these rows and those in our “building summary” is that what I called “reference” is now the registers’ address list, and it uses hexadecimal numbers instead of decimal -it’s easier for programmers to refer to addresses that way.

Let’s see if we can make sense of the information in those two rows:

  • We have two registers, ADCSRB and ADCL
  • What is ADCSRB?
    • page 120 says ADCSRB is short for ADC Control and Status Register B.
      • Page 119 says that ADC is the acronym for Analog to Digital Converter
    • page 120 describes the purpose of BIT6, ACME or Analog Comparator Multiplexer Enable
    • page 137 describes the purpose of BIN, or BIPOLAR Input Mode, and IPR, Input Polarity Reversal
    • page 138 describes the three ADTS, or ADC Auto Trigger Source
      • different combinations of these three bits select one of seven trigger sources
  • What is ADCL?
    •   Page 137 says it’s a data register, and describes how it holds part of the result of a readout from the Analog to Digital Converter
      • the individual bits are Read-only

If you haven’t downloaded the datasheet for your AVR microcontroller yet, do it now, find the Register Summary, and study a few registers in the same way I did above: this will give you a feel of how the information is organized. I printed the ATtiny85 datasheet and refer to it constantly.

 

MANIPULATING BITS

In the “building’s lights” analogy, if you want to manipulate a light, you first find its corresponding switch, then check whether it is ON or OFF, and finally flip it or leave it as is, according to your needs. Working with microprocessors, we need to figure out how to read the state of a bit within a group, and how to set that bit to 1 or 0.

To manipulate individual bits, we use Bitwise operations:

  • Given one bit,
    • the operation NOT returns the complement (the opposite).  If the bit is 0, it returns 1; if the bit is 1, it returns 0
      • the symbol for NOT is ~
  • Given two bits,
    • the operation AND returns 1 if the first and second bit are 1. Otherwise, it returns 0
      • the symbol AND operation is &;
    • the operation OR returns 1 if the first bit is 1, or the second bit is 1. Therefore it only returns 0 if both bits are 0
      • the symbol for OR is |
    • the operation XOR returns 1 if the first bit is 1 or the second bit is 1, excluding any other combination. Therefore, it returns 0 if both bits are 0, and it returns 0 if both bits are 1
      • the symbol for XOR is ^
  • Operations on bytes work on each corresponding bit, one by one:
    •     01 
      AND 11 
        = 01
  • A shift moves a bit to the left or right
    • The symbol for shift is << , or >> , depending on the direction

To find out the status of a bit in a register, we need an operation that returns 1 if that bit is 1, or 0 if that bit is 0, regardless of the state of the other bits

  • The operation we need is AND: we AND that particular bit with 1, and every other bit in the register with 0
    •     10011101   10010101
      AND 00001000   00001000
        = 00001000   00000000

To change the status of a bit to 1 (turn the switch ON), we need an operation that returns 1 regardless of the previous state of that bit, and regardless of the state of the other bits

  • The operation we need is OR: we OR that particular bit with 1, and every other bit in the register with 0
    •     10010101   10100101
       OR 00010000   00010000
        = 10010101   10010101

To change the status of a bit to 0 (turn the switch OFF), we need an operation that returns 0 regardless of the previous state of that bit, and regardless of the state of the other bits

  • The operation we need is, again, AND; in this case, however, we AND that particular bit with 0, and every other bit in the register with 1
    •     10010101   10100101
      AND 11101111   11101111
        = 10000101   11100101

To change the status of a bit to the opposite value, we need an operation that returns 1 if it was 0 and 0 if it was 1, regardless of the state of the other bits

  • The operation we need is XOR: we XOR that particular bit with 1, and every other bit with 0
    •     10011101   10010101
      XOR 00001000   00001000
        = 00000000   01101010

Even though in these examples we worked on a single bit, we can operate on as many bits as we want at once

  •     10010101   10100101
    AND 00001111   00001111
      = 00000101   00000101

The numbers we prepare so that our operations only affect the bits we want are called MASKS. I adapted the examples above from this Wikipedia article, which expands on the topic.

 

MANIPULATING REGISTERS

Let’s put all we know into practice! I’m going to show examples of things we can do using the “building’s lights” analogy, then I’ll go on to explain actual code from miniMO programs.

To manipulate registers, we refer to them by name, since they are memory mapped. This is thanks to a header file, a file that identifies them, which is included by the Arduino IDE during the build process (I add it to my programs, anyway: it’s <avr/io.h>).

NAME BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
APT1 ROOM2 ROOM1 ROOM0 BATHR KITCHEN LIVINGR HALL

 

 

 

  • If we want to turn all the lights OFF in apartment 1, we can write APT1 = 0x00; this turns all the bits in the register to 0
  • If we want to turn all the lights ON in apartment 1, we can write APT1 = 0xff
    • Our registers have 8 bits, so to turn all lights on we need to write all bits to 1, that’s to say 11111111
    • Binary number 11111111 equals decimal number 255, or hexadecimal number FF
      • In the expression APT1 = , the “equals” sign operates on decimal or hexadecimal numbers. We can’t write APT1 = 11111111 because the computer would believe we are operating on the number eleven million, one hundred and eleven thousand, one hundred and eleven
    • We could write 255, but programmers prefer to use hexadecimal numbers whenever bits are involved
      • Why not use bits? aside from the above, they can get long and it’s easy to make typos
      • Why not use decimal numbers? because at a glance you might be tricked into thinking that the code is working with regular numbers instead of bits
      • Why the 0x ? that’s so that the computer knows that what comes next is hexadecimal
      • Why the ff? it’s 255 in hexadecimal

Short detour on hexadecimal, or hex numbers:

  • This numerical system uses 16 symbols to represent numbers: 0 to 9, then a (10)  to f (15)
  • With four bits you can represent numbers 0 to 15; therefore, you can represent any 4-bit binary number with one of the 16 hexadecimal symbols
    • If you are dealing with many bits, you represent each group of four with the hex equivalent
      • binary 1111 1111 = hex f f ; binary 1111 0010 =  hex f 2
    • It is customary to translate all groups of four, even if the left-most group is 0
      • binary 0000 0010 = hex 02, written as 0x02
      • 0x13 = binary 0001 0011 = decimal 19

Back to manipulating registers:

NAME BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
APT1 ROOM2 ROOM1 ROOM0 BATHR KITCHEN LIVINGR HALL

 

 

 

  • To turn the lights ON in the kitchen, we set Bit 2 to 1: APT1 = APT1 | 0x04 (hex 04 is 0000 0100)
    • Instead of writing APT1 twice, we can use the equivalent APT1 |= 0x04
    • Instead of 0x04, or binary 0000 0100, we can use a number 1 shifted two positions to the left:
      APT1 |= (1 << 2)
    • Instead of using 2 in (1 << 2), we can refer to the name given to Bit 2 (thanks to the header file)
      APT1 |= (1 << KITCHEN)
  • To turn several lights ON, for instance the kitchen and room 1, we could write: APT1 = APT1 | 0x24 (hex 24 is binary 0010 0100). Other ways of doing the same:
    APT1 |= 0x24
    APT1 |= (1 << 5); APT1 |= (1 << 2); two lines of code
    APT1 |= (1 << 5) | (1 << 2); grouping operations in a single line
    APT1 |= (1 << ROOM1); APT1 |= (1 << KITCHEN); two lines of code
    APT1 |= (1 << ROOM1) | (1 << KITCHEN); grouping operations in a single line
  • To turn the lights OFF in the kitchen, we set Bit2 to 0: APT1 = APT1 & 0xfb (hex fb is 1111 1011)
    • Another way of writing 1111 1011 is performing the NOT operation on 0000 0100 (hex 04)
      APT1 = APT1 & (~0x04)
    • Once we’ve done this substitution, we can use all the shorthands we learned earlier:APT1 &= ~0x04
      APT1 &= ~(1 << 2)
      APT1 &= ~(1 << KITCHEN)
  • To turn several lights OFF, for instance the kitchen and room 1, we could write:APT1 = APT1 & 0xdb (hex db is binary 1101 1011). Other ways of doing the same:
    APT1 = APT1 & (~0x24)
    APT1 &= ~0x24
    APT1 &= ~(1 << 5); APT1 &= ~(1 << 2); two lines of code
    APT1 &= ~(1 << 5) & ~(1 << 2); grouping operations in a single line
    APT1 &= ~(1 << ROOM1); APT1 &= ~(1 << KITCHEN); two lines of code
    APT1 &= ~(1 << ROOM1) & ~(1 << KITCHEN); grouping operations in a single line

We can shorten these bit manipulations further with macros. A macro is an “instruction of instructions”, a shorthand to refer to a group of operations used frequently, to avoid typing them every time. Many macros are predefined in the AVR C library, while others are user-defined. Here are a few you’ll find very often:

  • Macro _BV: replaces (1 << (bit))
    • Instead of (1 << 2), we can write
      _BV(2)
      _BV(KITCHEN)
    • Instead of APT1 &= ~(1 << 5) & ~(1 << 2) we can write
      APT1 &= ~_BV(5) & ~_BV(2)
      APT1 &= ~_BV(ROOM1) & ~_BV(KITCHEN)
  • Macro sbi: replaces (port) |= (1 << (bit))
    • Instead of APT1 |= (1 << 2) we can write
      sbi(APT1, 2)
      sbi(APT1, KITCHEN)
  • Macro cbi: replaces (port) &= ~(1 << (bit))
    • Instead of APT1 &= ~(1 << 6) we can write
      cbi(APT1, 5)
      cbi(APT1, ROOM1)
  • sbi and cbi are no longer directly supported by the AVR library, but you may still see them at the beginning of programs, like so:
  • #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
    #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
  • The AVR C Library documentationlists current macros with the same purpose, along with several other macros that you will find from time to time
  • The macros cli() and sei() respectively enable and disable all interrupts

 

EXAMPLES FROM miniMO

It’s finally time to take a look at a few examples from miniMO code:

GTCCR = (1 << PWM1B) | (1 << COM1B1);In the General Timer/Counter Control Register, set PWM1B and COM1B1 to 1

  • The plain equal sign means that all other bits are set to 0. In this case this is fine, as their default value is 0 anyway
  • PWM1B enables Pulse Width Modulation in the Timer 1, and COM1B1 enables the output OC1B

OCR1C = 0xFF;Set the value of the Timer/Counter Output Compare Register C to 255

inputButtonValue = PINB & 0x02;Read the value of Bit 1 in the register PINB, and assign it to a variable named inputButtonValue

  • Bit 1 in PINB is PINB1, or digital input 1
  • This serves the same purpose as writing digitalRead(1)

ADMUX |= (1 << ADLAR);In the ADC Multiplexer Selection Register, set bit 5, ADLAR, to 1 (datasheet, page 134)

  • The Analog-to-Digital converter uses two eight-bit registers, ADCL and ADCH, to store the results of a conversion up to 10 bits of precision (page 137)
  • If you need 10-bit precision, you must first read 8 bits from ADCL, then two bits from ADCH, and put the results together as a single 10-bit number, in this order: ADCH (high) – ADCL (Low)
  • If ADLAR (Analog to Digital, Left-Adjust Result, by my own translation) is 1, the conversion data is adjusted so that you read just the 8 bits from ADCH, discarding the other two bits, now in ADCL. This gives you fast conversions, at the cost of those two bits of precision
  • By the way, here is a great article on ADLAR 🙂

 

Edits July 20th, 2021

  • Replaced various (1 << 6) by (1 << 5) under “Manipulating registers”. This error was kindly pointed out by Fred Byrman. Thank you!
  • Revised links to old Atmel’s site
Posted in Blog, Tutorials and tagged , , .

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.