Manipulating Registers in AVR Microcontrollers

  • Context: Programming AVR microcontrollers using the Arduino IDE
  • This article will be most useful to you if
    • you can write and understand basic programs
    • you would like to modify the setup of the timers or other peripherals within the microcontroller
    • you cannot 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 would be 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:

 REFNAMEBIT7BIT6BIT5BIT4BIT3BIT2BIT1BIT0Page
6BULBS   Data Registerpage 10
5APT1ROOM2ROOM1ROOM0BATHRKITCHENLIVINGRHALL
4APT0ROOM2ROOM0BATHRKITCHENLIVINGRHALL
3OFFICE0BROOM4ROOM3ROOM2ROOM1ROOM0MEETR1MEETR0
2OFFICE0ASERVERSBATHR1BATHR0KITCHENMAINHALL
1GroundLIFTSSTAIRSRECEPTION
0BasementALL

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):

AddressNAMEBIT7BIT6BIT5BIT4BIT3BIT2BIT1BIT0Page
0x04ADCL  ADC Data Register Low Bytepage 137
0x03ADCSRBBINACMEIPRADTS2ADTS1ADTS0pages 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
      • what is ADC? looking around, in page 119 I find 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 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, and 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 &amp;
      • 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>).

NAMEBIT7BIT6BIT5BIT4BIT3BIT2BIT1BIT0
APT1ROOM2ROOM1ROOM0BATHRKITCHENLIVINGRHALL
  • 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. That’s why we don’t write APT1 = 11111111
    • Even though we could write 255, programmers prefer to use hexadecimal numbers whenever bits are involved
      • Why not use bits? because they can get long, and it’s easy to make typos
      • Why not use decimal numbers? because in some cases, 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, so
    • You can represent any four-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. In practice you will see it with the prefix, written as 0x02
      • 0x13 = binary 0001 0011 = decimal 19

Back to manipulating registers:

NAMEBIT7BIT6BIT5BIT4BIT3BIT2BIT1BIT0
APT1ROOM2ROOM1ROOM0BATHRKITCHENLIVINGRHALL
  • 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 04, or binary 0000 0100, we can use a number 1 shifted two positions to the left:
      1 << 2APT1 |= (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 << 6); APT1 |= (1 << 2); two lines of code
    APT1 |= (1 << 6) | (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 << 6); APT1 &= ~(1 << 2); two lines of code
    APT1 &= ~(1 << 6) & ~(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 I’ve seen very often:

  • Macro _BV: replaces (1 << (bit))
    • Instead of (1 << 2), we can write
      _BV(2)
      _BV(KITCHEN)
    • Instead of APT1 &= ~(1 << 6) & ~(1 << 2) we can write
      APT1 &= ~_BV(6) & ~_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, 6)
      cbi(APT1, ROOM1)
  • sbi and cbi are no longer directly supported by the AVR library, but I’ve seen 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 documentation lists current macros with the same purpose, along with several other macros that you will see in programs from time to time
  • The macros cli() and sei() respectively enable and disable all interrupts

Finally, it’s 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
  • Since I use a plain equal sign, I’m also setting all other bits to 0. In this case this is fine, as their default value is 0
  • PWM1B enables Pulse Width Modulation in the Timer 1, and COM1B1 enables the output OC1B

OCR1C = 0xFF;

  • Sets 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 works in 10 bits, so it needs two eight bit registers to store the results of a conversion (page 137)
    • These registers are ADCL and ADCH. If you want to have 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 can get away with reading 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 🙂
Posted in Blog, Tutorials and tagged , , .

Leave a Reply

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