# Analog Sensors

In the interests of providing relevant information for these articles, some of these will cover the work I’m doing on the robot I’m building. This article will discuss analog inputs, such as those used by the ultrasonic ranging sensor (or URS, which we’ll focus on) and infrared distance sensor the robot is using. The example code is in `05_urs` directory in the example code. The URS uses ultrasonic “pings” to figure out how far objects in front of it are. The robot’s URS is constantly pinging, and the navigation system uses the distance measurement to determine if there are obstacles in its path and which direction it should turn. The navigation system wants a numeric value for distance, but the URS returns an analog value corresponding to the distance: a voltage between some reference value and ground that corresponds to the distance measured. The analog-to-digital converter takes this voltage and provides a numeric value.

Analog input, in general, works by providing a variable voltage on one of the analog pins. The ADC uses a reference voltage as a base to figure out what the highest possible value is, and the AVR’s analog-to-digital converter (ADC) converts between the analog voltage returned from the input to a digital value using a successive approximation ADC.

Using analog input is more involved than digital input; there are both hardware and software considerations that need to be factored in:

• The ADC requires a voltage reference. If your power supply is stable, you can just use the main power supply; this power supply must be within ±0.3V of the main power supply (page 250 of the datasheet).
• Analog inputs are limited by their resolution: how fine can the steps be? The ADC can be used in 10-bit resolution or 8-bit resolution. A 10-bit resolution can have values in the range of 0-1023. Let’s say we’re using an analog temperature sensor with a huge range of -40C to 80C. A 10-bit resolution means the temperature can at most be accurate to about a tenth of a degree (a 120C range divided by 1024 steps gives roughly 0.11C increments). An 8-bit resolution has values in the range of 0-255; our temperature sensor with an 8-bit resolution can be accurate to at most about half a degree (120C range divided by 256 steps gives a step size of about 0.46C).
• A 10-bit value requires a 16-bit variable; those can’t be read atomically while an 8-bit value can.
• Digital I/O is more or less instantaneous from the perspective of the user program; analog I/O is not, and requires the ADC to perform a conversion. In fact, the ADC takes 13 clock cycles to perform a conversion, and 25 cycles on first power up because it needs to initialise the internal circuitry. Page 253 of the datasheet explains this, and there are timing diagrams starting on page 254.
• The value that that comes from the ADC is the voltage read from the pin; typically the voltage needs to be manipulated to convert it into whatever the sensor is measuring. For example, the MaxSonar EZ4 URS, when using a 5V reference voltage, will return the voltage in roughly 10 mV steps such that the 8-bit number it returns directly represents the distance in inches to an object being picked up.
• The ADC can be triggered each time a conversion should be performed (e.g. each time a sensor value should be read), or it can be put into free-running mode in which it continually performs conversions. Every 13 cycles, a new value will be read (and after 25 cycles once the ADC is first started). The power budget of the hardware and the project requirements will determine which you use: continually performing conversions may not be the best choice for a system that runs on battery power; it might not be an issue for a device with a hardwired power supply, such as one using a mains power supply or running off a USB port.

Both of the sensors that are used for proximity measurement on the robot return a distance; with a digital pin (limited to logic highs and lows), they would have to communicate the distance over some sort of digital protocol (such as I2C or SPI), which requires the use of multiple pins, or communicate the distance by using up several pins to provide a binary distance value. The analog pins allow an input in the 8- or 10-bit range on a single pin; among other applications, this works well for distance measurements.

For the robot, the battery power supply is stable enough that it just uses that as the analog reference voltage.

## A small demo setup

Usually, when writing software in general it helps to build a test case or proof-of-concept of an idea before adding it into a larger system. In this article, we’ll mostly look at the PoC setup below: A Maxbotics EZ4 is connected directly to the Arduino; the robot actually uses an ATmega2560, but using the ATmega328P-based Uno works well for the proof-of-concept. There are only three connections needed: power, ground, and an analog signal connected to ADC3; the only code that needs to be written is the code for regularly reading distance measurements. With this particular sensor, setting it up like this will cause it to continually take ranging measurements.

## Initialisating the ADC

The URS can take readings once every 49ms, according to the datasheet. With that in mind, we’ll use a timer to regularly trigger pings. The ADC also has a “conversion completed” interrupt that we’ll use to update the latest readings from the URS. This use of interrupts will follow a general pattern: we’ll write background tasks as interrupt-driven components.

Here’s the ADC initialisation code:

``````
#define URS_CHANNEL	ADC3D

/*
* init_ADC prepares the ADC for use with the URS.
*/
static void
init_ADC(void)
{
/* Use Vcc (the main power supply) as the reference. */
ADMUX = _BV(REFS0);

/* Left-align the results, which gives 8-bit precision. */
ADMUX |= _BV(ADLAR);

/* Select the URS in the channel multiplexor.*/
ADMUX |= URS_CHANNEL;

/*
* We really don't need a high sample rate, so we use a
* high prescale. A prescale of 128 with a 16 MHz clock
* means it samples at a rate of 125 kHz.
*/
ADCSRA = _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);

/* Enable the ADC. */
ADCSRA |= _BV(ADEN);

/* Disable digital inputs on the URS channel. */
DIDR0 |= _BV(URS_CHANNEL);

/* Kick off the first conversion. */
ADCSRA |= _BV(ADSC);
}``````

The first thing that needs to be done is to set up the ADC multiplexor. Only three bits are used to select channels on the ATmega328P (see page 263 of the datasheet); the others are used in the configuration of the ADC. The `REFS0` bit uses the main power bus as the reference voltage; it expects a capacitor to be connected to AREF (this is a decoupling capacitor used to smooth out power noise; Designing Embedded Hardware has a good explanation of decoupling capacitors). The demo doesn’t use one, but in a complex system, it’s a good idea.

The URS datasheet also mentions that the sensor returns values in the range of 0-255; we can tell from this that we only need 8-bit precision. The `ADLAR` bit tells the microcontroller to left-align results (see page 265); normally, the ADC provides 10-bit resolution, which requires a 16-bit register to store the results of the conversion. Reading a 10-bit result from the ADC is done with `value = ADCL + (ADCH << 8)` (page 265 notes that `ADCL` must be read before `ADCH`). However, for an 8-bit result, we only need to read `ADCH`. You’ll need to consult the datasheet for your sensor to determine what your conversion size requirements are.

The last part of setting up `ADMUX` is to set up the channel (the ADC term for which pin is being used) that will be used for the conversion; only one can be converted at a time. We’ll discuss a strategy later for building systems with multiple analog inputs. Page 263 gives the appropriate multiplexor bit for the channel the sensor is connected to (`ADC3`). One of the common mistakes that I’ve found myself making here is to set the bit with `_BV(URS_CHANNEL)`: this sets bit 3, which is actually `ADC8` in this case. It will virtually always result in the wrong channel being selected.

The ADC has two status registers; the B register contains conversion triggering setup that we won’t use here.

Similar to the timer, the ADC has a prescalar: this controls how fast the results are sampled. The sensor isn’t high precision, so we’ll stick to the highest prescaler value to reduce the burden on the microcontroller and save power. A prescaler value of 128 with the clock frequency of 16 MHz gives us a sample rate of 125 kHz, and it’s selected by setting bits `ADPS2..0`. The ADC enable bit (`ADEN`) turns on the ADC; in a low-power setting, this could be disabled when not in use as long as the increased time required for the first conversion is acceptable.

The `DIDR` register controls the digital buffer for the ADC pins: page 266 notes that the “corresponding PIN Register bit will always read as zero when this bit is set… to reduce power consumption.”

After setting up the ADC, the first conversion is kicked off: this will allow us to avoid the slower first read later. Note that interrupts aren’t enabled, and we haven’t enabled the ADC conversion completion interrupt yet, so no interrupt will be triggered when this is complete. Its only purpose is to “prime” the ADC.

We’ll also use a timer to control the ADC; however, the URS’s refresh rate of 49ms means we have to adjust the prescaler. A prescaler of 8 requires `OCR1A` to be set to 98000, which is too large to fit into the 16-bit register. We’ll bump up the prescaler to 64; now, 4 microseconds elapse for every timer tick which means `OCR1A` should be set to 12250 (0.25 ticks / μs × 49000 μs → 12250 ticks).

``````static void
init_timer1(void)
{
/*
* Set the waveform generation mode to CTC with OCR1A as the
* top value.
*/
TCCR1B |= _BV(WGM12);

/* Use a prescaler of 64. */
TCCR1B |= _BV(CS11) | _BV(CS10);

/* Trigger an interrupt on output compare A. */
TIMSK1 |= _BV(OCIE1A);

/* Set the output compare register to the update interval. */
OCR1A = URS_CYCLE;
}``````

## Timer ISR

The timer is used to trigger conversions: the URS can only provide readings every 49ms, after which a conversion should be kicked off.

``````/*
* Timer1 is set up with a prescaler of 64, which means 4 microseconds
* per tick. The URS can range once every 49ms, which translates
* to a timer value of 12250.
*/
#define URS_CYCLE	12250

/*
* The Timer1 ISR triggers an ADC conversion every URS_CYCLE ticks.
*/
ISR(TIMER1_COMPA_vect)
{
/* Clear pending ADC interrupts. */
ADCSRA |= _BV(ADIF);

/* Trigger an ADC conversion. */
ADCSRA |= _BV(ADSC);

/* Enable the ADC interrupt. */
ADCSRA |= _BV(ADIE);
}``````

The ISR should clear out any pending ADC interrupts first, then begin the process of kicking off a conversion. Setting the `ADSC` bit in `ADCSRA` (“ADC start conversion”) begins the conversion, and setting `ADIE` enables the ADC conversion complete interrupt.

## The ADC ISR

Once a conversion has completed, we’ll want to store the result of the conversion and turn off the ADC interrupt until the next time it is triggered.

``````struct reading {
/* val contains the last measurement from the ADC. */
uint8_t		val;

/* count stores the number of conversions that have occurred. */
uint16_t	count;
};
volatile struct reading	sensor = {0, 0};

ISR(ADC_vect)
{
/*
* Read the distance measurement into the sensor readout
* structure.
*/
sensor.val = ADCH;
sensor.count++;

/*
* Turn off the ADC interrupt and clear any pending
* interrupts.
*/
ADCSRA |= _BV(ADIF);
ADCSRA &= ~_BV(ADIE);
}``````

The reason a count is provided with the value is so that other parts of the program can determine if a new measurement has been taken (by comparing to a previous count value) or if measurements have occurred (the count will be non-zero). The sensor only stores the value of `ADCH` because it is an 8-bit left-aligned value, otherwise it would need to get the value of the ADC as described previously. The `avr/io.h` header also defines the `ADC` macro that reads the 16-bit value correctly. We could do that here, as the value will always be in the 8-bit range, but it’s better to keep the code clear as to exactly what it’s doing. Reading the code later and seeing that only `ADCH` is being read will remind future you that the sensor only uses 8-bit resolution.

## Great success

The final program (in `05_urs`) reports distance measurements using the serial port:

``````Terminal ready
Boot OK.
URS reading #   50: 37
URS reading #  101: 37
URS reading #  152: 37
URS reading #  204: 37
URS reading #  255: 37
URS reading #  306: 37
URS reading #  357: 3``````

The distance changed when I waved my hand in front of the sensor, though it’s difficult to see from over there. You’ll have to take my word for it (or build this example).

## Dealing with multiple analog inputs

The ADC can only perform a conversion on one channel at a time. What if multiple sensors are being used? One approach, if they’re all using the same timer cycle, is to set up an array of inputs, and cycle through them.

``````/*
* The first three ADC channels (ADC0, ADC1, ADC2) are used.
*/
#define CHANNEL_COUNT	3
static uint16_t	readings[CHANNEL_COUNT] = {0, 0, 0};
static uint8_t  channel = 0;

ISR(ADC_vect)
{
/*
* If there are channels still left to be read, then
* set the ADC to the new channel and kick off a new
* conversion.
*/
if (channel != sizeof(channels)) {
readings[channel] = ADC;
channel++;

/*
* Clear the channel selection bits and set the active
* channel.
*/
ADMUX = (ADMUX & 0xF0) | channel;
ADCSRA |= _BV(ADSC);
}
/*
* If all the channels have been read, then disable the
* interrupt.
*/
else {
/*
* Turn off the ADC interrupt and clear any pending
* interrupts.
*/
ADCSRA |= _BV(ADIF);
ADCSRA &= ~_BV(ADIE);

/* Reset the channel counter. */
channels = 0;
}
}``````

In order to support this, we need to store a list of the last value from the ADC. In order to save on memory, we’re going to use the ADCs starting at 0 and going to `CHANNEL_COUNT`. This allows us to save some memory by not having to store channel IDs.

Inside the ISR, we’re going to chain interrupts in a way. First, we need to get the reading from the ADC (remember, the ISR is called when the conversion is done and a value is ready). Then we’ll move to the next channel and kick off the next conversion, which will trigger another interrupt on completion. As part of the channel setting, we clear the bottom four bits, which are used to select the ADC channel in our code; that is, unless the onboard temperature sensor is being used. This is a bit more work, but is well covered in section 23.8 of the datasheet.