therefore referenced by its port and pin number, prefixed
with a “P” (e.g., PB1, PB0, PD7). Figure 2 is from the
ATmega328P datasheet, and shows how the individual
pins are named — you’ll notice that they don’t flow
logically from the first to the 28th pin, not all ports have
eight pins, and there’s no PORTA.
When I first looked at the pinout diagram in the
datasheet, I nearly flipped! There was so much complexity
and very little to explain what all the acronyms in
parentheses were. Don’t panic! We’ll tackle these when
we need them over the course of this series.
Okay, so we know that each of the I/O pins are
numbered differently to the Arduino IDE and how they’re
numbered. How do we read from or write to them? Most
microcontrollers use registers to do this. A register (or
more correctly, a hardware register) is a bit like a predefined variable — you can read the value stored in the
register and change it (if it’s not a read-only register).
In the background, each hardware register is linked to
specific hardware-related functionality, and writing to them
causes the hardware to behave in certain ways. Registers
have specific memory addresses, but we usually refer to
them using the “friendly” names that are defined in the
datasheets and header files.
The simplest hardware registers are simply called
“PORT” registers. There is one of these registers for each
of the ports on the MCU. For the ATmega328P, there are
PORTB, PORTC, and PORTD registers. By writing to these,
you cause the individual pins to go either high or low in
the same way as digital Write() did in the Arduino IDE.
But which pin? A port contains up to eight pins, so
how does the PORT register allow us to access a specific
pin? Simple! Or, so I was told when I first tackled this.
However, it took me a while.
As mentioned, each port has up to eight pins. A byte
has eight bits. A register is usually one byte in size. If you
make the connection, you’ll realize that each bit in the
eight-bit register is linked to a pin on that port. Figure 3
shows an example of how this would look for PORTB,
with PB0 and PB4 set high.
In order to make pin PB2 go high, you need to set bit
2 of the PORTB register to a 1. To make PB4 go low, you
need to set bit 4 of the PORTB register to 0. Behind the
scenes, this is exactly what digital Write() was doing for you.
One final piece of theory before we get stuck into a
first project: bitwise operations. We need to use bitwise
operations in order to set the bits in the PORT to 1 or 0.
Bitwise operations are used to manipulate individual
bits, or groups of bits. We won’t go into all the uses for
these operations and operators here, but will focus on
what we need them for: to set specific bits in registers.
Let’s assume you have PORTB configured as in Figure
3; bits 0 and 4 are set to 1 and the rest to 0. In binary, this
would be represented as 0b00010001 (where the prefix
0b indicates a binary number follows). Now, let’s set PB2
high (i.e., bit 2 will be set to 1).
One way to do this is by spelling it out and setting
PORTB to 0b00010101. However, you can only do this if
you keep track of the values of all the other bits in the
register — i.e., you also need to know the values of the bits
that you aren’t setting. This is not very practical in a
dynamic embedded system. Bitwise operators allow us to
change the value of just PB2, without affecting (or
needing to know) the values of the other bits in the
register. Bitwise operation is critical in working with
You may have come across the bitwise operators
(AND, NOT, OR, XOR), as well as the shift operators (
Left-Shift and Right-Shift). I want to get us to a working project
as soon as possible, so I won’t delve into the detailed
theory behind these operators. What I will do is quickly
summarize what you need to know to get working.
The bitwise OR operator compares two binary values
and returns a value that includes the 1s from both of
them. In the example in Figure 4, bit 2 contains a 0 in the
original register, and a 1 in the value it is being OR’ed
with; the result is therefore a 1. Practically, if we want to
set PB2 to a 1, we simply need to perform the OR
operation: PORTB = PORTB | 0b00000100.
The bitwise AND operator compares two binary
values, and returns a value that only includes the 1s
where both of the values contain a 1. In the example in
Figure 4, we want to check whether bits 2 and 0 are set
(i.e., are 1).
By ANDing a binary value of 0b00000101 (where
bits 2 and 0 are 1), we can see that only bit 0 was set in
the original register. In practice, this would look like
PORTB & 0b00000101
40 April 2015
Pin Number PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
BitNumber 7 6 5 4 3 2 1 0
Pin High/Low Low Low Low High Low Low Low High
PORTB Register000 1 0001
FIGURE 3: Register for PORTB with PB0 and PB4