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 byte can be thought of as a row of eight related switches in a 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
- page 120 says ADCSRB is short for ADC Control and Status Register B.
- 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
- 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
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 returns1
; if the bit is1
, it returns0
- the symbol for NOT is
~
- the symbol for NOT is
- the operation NOT returns the complement (the opposite). If the bit is
- Given two bits,
- the operation AND returns
1
if the first and second bit are1
. Otherwise, it returns0
- the symbol AND operation is
&
;
- the symbol AND operation is
- the operation OR returns
1
if the first bit is1
, or the second bit is1
. Therefore it only returns0
if both bits are0
- the symbol for OR is
|
- the symbol for OR is
- the operation XOR returns
1
if the first bit is1
or the second bit is1
, excluding any other combination. Therefore, it returns0
if both bits are0
, and it returns0
if both bits are1
- the symbol for XOR is
^
- the symbol for XOR is
- the operation AND returns
- 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 with0
-
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 with0
-
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 with1
-
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 with0
-
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 to0
- 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
- In the expression APT1 = , the “equals” sign operates on decimal or hexadecimal numbers. We can’t write
- 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
- Our registers have 8 bits, so to turn all lights on we need to write all bits to 1, that’s to say
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 ; binary1111 0010
= hex f 2
- binary
- It is customary to translate all groups of four, even if the left-most group is
0
- binary
0000 0010
= hex 02, written as0x02
0x13
= binary0001 0011
= decimal 19
- binary
- If you are dealing with many bits, you represent each group of four with the hex equivalent
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 is0000 0100
)- Instead of writing APT1 twice, we can use the equivalent
APT1 |= 0x04
- Instead of
0x04
, or binary0000 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)
- Instead of writing APT1 twice, we can use the equivalent
- To turn several lights ON, for instance the kitchen and room 1, we could write:
APT1 = APT1 | 0x24
(hex 24 is binary0010 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 is1111 1011
)- Another way of writing
1111 1011
is performing the NOT operation on0000 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)
- Another way of writing
- To turn several lights OFF, for instance the kitchen and room 1, we could write:
APT1 = APT1 & 0xdb
(hex db is binary1101 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)
- Instead of
- Macro sbi: replaces
(port) |= (1 << (bit))
- Instead of
APT1 |= (1 << 2)
we can write
sbi(APT1, 2)
sbi(APT1, KITCHEN)
- Instead of
- Macro cbi: replaces
(port) &= ~(1 << (bit))
- Instead of
APT1 &= ~(1 << 6)
we can write
cbi(APT1, 5)
cbi(APT1, ROOM1)
- Instead of
- 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 documentation
lists 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 is0
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