The 0001, 0010, and 0011
Let’s get into the 1, 2, 3 of analog by working through
the steps needed to read an analog value from pin PC2.
Why PC2? No real reason, other than it’s an analog pin.
Analog Pins and Channels
There are six analog pins on the ATmega328P-PU:
PC0 through PC5. These can be identified as analog by
the labels ADC0 to ADC5 on the pin configuration
diagram (refer again to Figure 1). If you read the
datasheet, you probably picked up that there are, in fact,
nine analog channels: 0 to 8. It originally took me a while
to find the missing channels, but looking through the
sheet I found that channels 6 and 7 are not available on
the 28-pin PDIP package that we use on our breadboard.
They are only on the 32-pin packages (MLF and TQFP).
Channel 8 is available to use, and is connected to the
chip’s internal temperature sensor. However, this sensor
only has an accuracy of ± 10°C, so it has limited uses.
Typical Process Flow
Let’s go through the process flow for a single
conversion triggered in code; in other words, we will
instruct the code to take a single reading without relying
on the continuous or triggered modes I touched on
earlier. I’ve mapped out the process flow in Figure 4 for
reference as we go.
Firstly, we will initialize the ADC before entering the
main while(1) loop in the code. This is normally a once-off
configuration step, and is something that we’ll do in the
future with other peripherals on the microcontroller.
Secondly, we’ll initiate the conversion and then wait
for it to complete — either in a loop or by using an
interrupt. In this example, we’ll keep things straightforward
and use a loop to wait for the completion.
Finally, we’ll read and interpret the value, and then do
something with it.
Getting a Handle on Registers
To do all this initializing, conversion, and reading, we’ll
need to use a number of registers. These are used in the
same way as the Data Direction, Digital Output, and
Digital Input registers we’ve used so far, but get a little
more complex. Each of the bits in the registers we’ve used
already relate directly to a pin on the controller. The
registers we’ll be using here don’t relate to pins, but
instead either control settings or allow you to read values.
Here’s where the dreaded datasheet comes in handy!
Figure 5 shows an excerpt from the datasheet. The
table at the top shows each bit in the register, a cryptic
abbreviation of what the bit does, whether it is read/write,
and finally the initial value at power-on. Below the table is
a more detailed description of each bit or range of bits. As
we work through the steps, refer to the relevant registers
in the datasheet to see how they are used.
Using Include Files
Often, when writing a set of related functions, it is
helpful to group them into separate files. For example, we
could create an ADC.c and an ADC.h file to contain all
the ADC-related functionality and variables. This both
reduces the complexity of the main program file, and
allows you to re-use all those ADC functions in other
programs. We’ll work through the details of creating these
files in a later article, so if this example seems a little
unstructured with all the code in a single “c” file, bear in
mind that there are better ways to design your project.
Step 1: Initialize the ADC
This is the most complicated step in the process, so I
usually wrap it up in a separate function that I call from
my main routine. Once the ADC is configured, it is quite
May 2015 41
Figure 4: Overview of the ADC process flow.
Figure 5: Datasheet extract showing a typical