9. ATmega328P Hardware: ADC and USART – Arduino Software Internals: A Complete Guide to How Your Arduino Language and Hardware Work Together

© Norman Dunbar 2020
N. DunbarArduino Software Internalshttps://doi.org/10.1007/978-1-4842-5790-6_9

9. ATmega328P Hardware: ADC and USART

Norman Dunbar1 
(1)
Rawdon, West Yorkshire, UK
 

This chapter continues our look at the various hardware components of the ATmega328P. Some of them are not visible (“surfaced”) in the Arduino IDE or Language, so they may at first appear new to you – the Analogue Comparator, for example.

The information in this chapter and Chapter 8 should link up with what you have already seen in Chapters 2 and 3 which covered the compilation process and the Arduino Language.

9.1 The Analogue Comparator

The ATmega328P has a built-in device, called the Analogue Comparator, which compares the input voltage on pin AIN0, the positive input, and pin AIN1, the negative input.

If the voltage on the positive input, AIN0, is higher than the voltage on the negative input, AIN1, the Analogue Comparator output bit, ACO (that’s a letter OH and not a digit zero), in the Analogue Comparator Control and Status Register (ACSR), will be set to 1binary.

If the voltage on the positive input, AIN0, is lower than the voltage on the negative input, AIN1, then ACO is cleared to 0binary.

In addition to setting the ACO bit , the comparator can also be set to trigger
  • The Timer/counter 1 Input Capture function.

  • A dedicated interrupt, exclusive to the Analogue Comparator. The interrupt can be configured to trigger when
    • The comparator output, ACO, is rising – from 0binary to 1binary.

    • The comparator output, ACO, is falling – from 1binary to 0binary.

    • The comparator output, ACO, is toggling – from 0binary to 1binary or from 1binary to 0binary.

Chapter 8, Section 8.3, “Input Capture Unit,” deals with Timer/counter 1’s Input Capture Unit.

Digital pins D6 and D7 are the Arduino’s comparator input pins, with D6 being AIN0, the positive input, and D7 being AIN1, the negative input. D6 is therefore the reference voltage to which the voltage on D7 can be compared. However, AIN1 can optionally be configured to be any one of the ADC input pins, A0–A7 (if you have A6 and A7 of course!), as explained in the following.

The Arduino Language does not facilitate easy access to the Analogue Comparator.

You have to do it the hard way yourself, by manipulating the individual register bits – there’s no easy option here I’m afraid!

9.1.1 Reference Voltage

The reference voltage, on the positive input, AIN0, is used as the basis for the comparator. The voltage that is being sampled or compared will be checked against the voltage on the comparator’s positive input. The positive input can be configured to be either
  • An internally generated 1.1 V known as the bandgap reference voltage

  • An external voltage supplied on the AIN0 pin also known as D6

9.1.2 Sampled Voltage

The voltage being compared against the reference voltage can be either
  • The AIN1 pin, also known as D7

  • Any one of the ADC input pins, which on the Arduino are A0–A5 plus A6 and A7 if your board has the surface mount version of the ATmega328P and the manufacturer has routed those two extra pins to a header somewhere

9.1.3 Digital Input

The pins D6 and D7 cannot be used as normal I/O pins when being used by the comparator. To this end, they should have their I/O buffers disabled – to save wasting power. This is done by setting bits AIN0D and/or AIN1D in register DIDR1, the Digital Input Disable Register 1.

Bit 0 in the DIDR1 register is AIN0D, bit 1 is AIN1D, and bits 2–7 are reserved and should not be written. They will always be zero when read.

When either AIN0D or AIN1D bit is set to 1binary, the digital I/O on the AIN0 (D6) or AIN1 (D7) pin is disabled. When the pins are disabled in this manner, reading the PIND register (the input register that these two pins belong to) will always return a value of 0binary for whichever of the two pins has been disabled.

The ATmega328P data sheet has this to say:
  • When an analog signal is applied to the AIN1/0 pin and the digital input from this pin is not needed, this bit should be written logic one to reduce power consumption in the digital input buffer.

The following sections summarize the steps necessary to use the comparator.

9.1.4 Enable the Analogue Comparator

  • Write a 0binary to bit ACIE in the Analogue Comparator Control and Status Register – ACSR. This disables the Analogue Comparator Interrupt Enable as an interrupt can occur when the ACD bit is changed.

  • Write a 0binary to bit ACD in ACSR.

The preceding steps enabled the comparator and disabled interrupts from it, for now. This can be easily changed later if interrupts from the comparator are required.

9.1.5 Select Reference Voltage Source

The reference voltage applied to the positive input to the comparator can be either
  • The internal bandgap reference voltage

  • An external voltage on pin AIN0

Only one of these can be selected.

9.1.5.1 External Reference

To use the external reference voltage on pin AIN0, you must
  • Write a 1binary to bit AIN0D to disable the I/O facilities on pin D6.

  • Write a 0binary to bit ACBG in the ACSR register.

9.1.5.2 Internal Bandgap Reference

To use the internal reference voltage instead of AIN0, you must
  • Write a 1binary to bit ACBG in register ACSR.

9.1.6 Select Sampled Voltage Source Pin

The voltage to be compared with on the comparator’s positive input can be either
  • Pin AIN1

  • One of the pins A0–A7

9.1.6.1 Sample Voltage on Pin AIN1

To compare an external reference voltage on pin AIN1 (D7) , you must
  • Write a 1binary to bit AIN1D, in register DIDR1, to disable the I/O facilities on pin D7.

Then, either
  • Write a 0binary to bit ACME in register ADCSRB.

  • Write a 1binary to bit ACME in register ADCSRB and write a 1binary to bit ADEN in register ADCSRA.

You therefore have two choices to set up the system to use AIN1.

9.1.6.2 Sample Voltage on Pins A0A7

  • Write a 0binary to bit PRADC in register PRR to power the ADC.

  • Write a 0binary to bit ADEN in register ADCSRA to disable the ADC from using the ADC multiplexor.

  • Write a 1binary to bit ACME in register ADCSRB to allow the comparator to use the ADC multiplexor.

  • Write the pin number, 0–7, to bits MUX2:0 in the ADMUX register to select the desired input pin from A0 through A7.

9.1.7 Sampled Voltage Summary

Table 9-1 summarizes the pin settings for all possible negative input settings.
Table 9-1

Analogue Comparator negative input summary

ACME

ADEN

MUX2-MUX0

Negative Input

0

?

???

AIN1

1

1

???

AIN1

1

0

000

ADC0

1

0

001

ADC1

1

0

010

ADC2

1

0

011

ADC3

1

0

100

ADC4

1

0

101

ADC5

1

0

110

ADC6

1

0

111

ADC7

9.1.8 Comparator Outputs

So far, we have looked at the numerous ways that we can set up the two inputs to the comparator. How then to we get an output from it? The Analogue Comparator Control and Status Register (ACSR) is where we need to be looking. We have already seen that bits ACD and ACBG disable/enable the comparator and select the reference voltage. The other bits areas follows:
  • ACO, Analogue Comparator Output – This bit is connected to the comparator output and is synchronized with the comparator output when it changes. This synchronization takes one to two clock cycles to settle and delays changing this bit when the comparator changes.

  • ACI, Analogue Comparator Interrupt Flag – This bit is set when a comparator output triggers according to the mode set in bits ACIS1:0 (see in the following). If global interrupts are enabled and the ACIE bit set, then the appropriate ISR will be executed and this bit cleared by hardware. Otherwise, it can be cleared by the writing of a 1binary in the normal back-to-front AVR manner!

  • ACIE, Analogue Comparator Interrupt Enable – This bit enables or disables the firing of an interrupt when the comparator output takes on a certain state as defined by bits ACIS1-0 which are described in the following. When the bit is 0binary, no interrupts will fire.

  • ACIC, Analogue Comparator Input Capture Enable – Writing this bit to 1binary will enable Timer/counter 1’s Input Capture function to be triggered by the Analogue Comparator. In addition to setting this bit, bit ICIE1 in register TIMSK1 must also be set to enable the Timer/counter 1 Input Capture interrupt.

  • ACIS1-0, Analogue Comparator Interrupt Mode Select – These two bits must always be changed after disabling the comparator’s ACIE bit by writing it to 0binary. These two bits select the interrupt mode for the comparator and determine when the interrupt will be fired. The possible values are
    • 00binary – The interrupt fires when the comparator output toggles.

    • 01binary – Reserved – do not use.

    • 10binary – Interrupt fired on falling output edge (when the ACO changes from 1binary to 0binary).

    • 11binary – Interrupt fired on rising output edge (when the ACO changes from 0binary to 1binary).

9.1.9 Comparator Example

The sketch in Listing 9-1 shows the use of the Analogue Comparator to turn an LED on or off depending on whether the voltage at D6 is higher or lower than the voltage at D7.

In the circuit I used for this experiment, I created a voltage divider using two resistors. I used the same value, but it isn’t necessary. The output from this was fed into D6 and used as the reference voltage. I connected one end to the Arduino VCC (5 V) and the other to Arduino ground.

I also wired up a potentiometer to Arduino VCC and ground and fed the middle pin of the potentiometer to pin D7. By turning the potentiometer, I was able to vary the voltage on pin D7, and the LED turned off or on depending on whether the voltage was higher on D7 or lower.

You can see the breadboarded circuit in Figure 9-1.
Figure 9-1

Analogue Comparator circuit

When the project was running, turning the potentiometer varied the voltage on D7. If the reference voltage on D6, which was 2.5 V due to my voltage divider, was higher than the variable voltage on D7, the LED turned on; otherwise, it turned off.

I’ve added a bigger LED to pin D13 to better show the effect, but you don’t have to do this. Just remember there are two LEDs on D13 and you need to keep the current below 20 mA.

The LED I’m using has a voltage drop of 1.8 V, so subtract that from the 5 V supply from the Arduino to get 3.2 V. The resistor is 330 Ohms; and so, by division, we get 3.2/330 which gives 9.69 mA.

The Arduino has an absolute maximum of 40 mA per pin (but with other restrictions – see Appendix C for details), but 20 mA is preferred, so there should be no problems with this resistor value.
//=======================================================
// This sketch uses the analogue comparator with pin D6
// as the reference voltage and D7 as the voltage to be
// compared with D6. When D6 is higher than D7 then the
// LED will light. When D6 is lower than D7, the LED goes
// out. So, not a blink sketch this time!
//=======================================================
// This function sets up the comparator to fire an interrupt
// each time the ACO bit toggles. It uses D6 as the reference
// voltage and D7 as the voltage to be compared.
void setupComparator() {                          ①
    // Disable AC interrupts.
    ACSR &= ~(1 << ACIE);
    // Enable AC by disabling the AC Disable bit!
    ACSR &= ~(1 << ACD);
    // Disable digital I/O on D6 and D7.
    DIDR1 |= ((1 << AIN0D) | (1 << AIN1D));
    // D6 will be the reference voltage.
    ACSR &= ~(1 << ACBG);
    // D7 to compare with D6.
    ADCSRB &= ~(1 << ACME);
    // Fire AC interrupt on ACO toggle.
    ACSR |= ((0 << ACIS1) | (0 << ACIS0));
    // Enable AC Interrupt.
    ACSR |= (1 << ACIE);
    // Enable Global Interrupts.
    sei();
}
void setup() {
    // You can still use Arduino code as well -
    // but I'm not! D13 = output.
    DDRB |= (1 << DDB5);                          ②
    setupComparator();
}
void loop() {
    ; // Do nothing.                              ③
}
// Analogue Comparator Interrupt Handler.Reads the ACSR
// register and sets the LED to the state of the ACO bit.
ISR(ANALOG_COMP_vect) {                            ④
    if (ACSR & (1 << ACO)) {
        PORTB |= (1 << PORTB5); // LED HIGH);
    } else {
        PORTB &= ~(1 << PORTB5); // LED LOW);
    }
}
Listing 9-1

Analogue Comparator sketch

  •      ①     The setupComparator() function initializes the Analogue Comparator as described in the text and the code comments.

  •      ②     This is just a very short pinMode(LED_BUILTIN, OUTPUT) call.

  •      ③     The loop() function does nothing. Everything happens in the ISR.

  •      ④     The ISR fires any time that the Analogue Comparator output toggles. It sets the LED to on, if the ACO bit is set; otherwise, it turns the LED off.

You should note that when the comparator output is set or reset, it remains that way until a change is necessary. The preceding interrupt is only called when the ACO bit toggles from set to clear, or from clear to set.

It is effectively, like a light switch, on or off until it changes.

9.2 Analogue to Digital Converter (ADC)

The Atmega328P has a single, 10-bit Analogue to Digital Converter which has up to nine separate inputs, depending on which ATmega328P your Arduino is using. The Dual In-Line Package (DIP) with 28 pins has seven inputs, while the surface mount version has all nine. On Arduino boards, these are pins A0–A5 (or A0–A7 for the surface mount version), plus the internal temperature sensor input. The ADC inputs can also be used by the Analogue Comparator as described in Section 9.1, “The Analogue Comparator,” at the start of this chapter.

As mentioned earlier, the ADC has 10-bit resolution which means that it can return a value between 0 and 210 – 1, or 1023. The value is indicative of the voltage on the AREF or AVCC pin, depending on configuration, as compared with whichever ADC input has been selected. Only one of the available ADC inputs can be used at a time. A result of zero represents GND, and 1023 represents the reference voltage. The reference voltage can be configured to one of the following three options:
  • The 1.1 V internal bandgap reference voltage

  • The voltage on the AVCC pin

  • An external voltage on the AREF pin, which must not exceed VCC

The voltage on the ADC input pin is therefore
(Reference Voltage / 1024) * ADC Result

The ADC works by using a capacitor to sample and hold the input voltage, which is useful if the voltage changes during the time that the ADC is still calculating the result as it ensures that the sampled voltage remains stable throughout the calculation. The ADC uses successive approximation to calculate the 10 bits of the result.

In order to further improve the accuracy of the ADC, there is a special sleep mode, ADC Noise Reduction, which shuts down almost everything which is not the ADC, in order to stop all the "digital noise" that the microcontroller generates internally, so that the ADC can do its job in relative peace and quiet.

The ADC can be configured to take a single shot – as per the Arduino analogRead() function call – where ADC conversions are started manually on request, or to run in free running mode where each completed conversion starts another conversion automatically and only the very first conversion has to be manually started.

The ADC has an interrupt that may be configured to fire when the conversion is complete to avoid the need for your code to sit in a polling loop, waiting for the result. The interrupt is required in auto trigger mode, which can be triggered by one of many different sources – more on that later.

9.2.1 ADC Setup and Initiation

Assuming you are not using the Arduino’s analogRead() function, then the following steps are required in order to take an ADC reading:
  • Power up the ADC.

  • Select a suitable prescaler to configure the ADC to run at a frequency within its required range of 50–200 KHz.

  • Select a suitable reference voltage source.

  • Decide on whether the result is to be left or right aligned.

  • Select an input source.

  • Disable digital input for the selected input pin.

  • Enable interrupts, if required.

  • Select single-shot or auto trigger, and if auto trigger, choose a trigger.

  • Enable the ADC and initiate a conversion.

Easy?

9.2.1.1 Powering the ADC

Probably the easiest step of all, you simply have to write a 0binary to the PRADC bit of the PRR register. If that bit is a 1binary, then the ADC is powered off:
PRR &= ~(1 << PRADC);

9.2.1.2 Selecting the Prescaler

The ADC runs most accurately at a frequency between 50 and 200 KHz. This frequency range is mandatory if you wish to get the full 10-bit result; however, if you require less than 10-bit resolution, you can run the ADC at different frequencies. On an Arduino, the CPU is running at 16 MHz, which is a tad on the high side for the ADC. There is a prescaler for the ADC to bring the frequency down to within the desired range. To set the prescaler, you must write a suitable value to the ADPS2, ADPS1, and ADPS0 bits in the ADC Control and Status Register A, ADCSRA.

Table 9-2 shows the permitted settings for the prescaler and the resulting ADC frequencies, in KHz, for 16 MHz and 8 MHz systems.
Table 9-2

ADC prescaler settings and frequencies

ADPS2–ADPS0

Description

16 MHz

8 MHz

000

Divide F_CPU by 1

16,000

8,000

001

Divide F_CPU by 2

8,000

4,000

010

Divide F_CPU by 4

4,000

2,000

011

Divide F_CPU by 8

2,000

1,000

100

Divide F_CPU by 16

1,000

500

101

Divide F_CPU by 32

500

250

110

Divide F_CPU by 64

250

125

111

Divide F_CPU by 128

125

63

On Arduinos, only the final 111binary setting, divide by 128, brings the ADC frequency down into the desired range. You might be successful with the 110binary setting, divide by 64, which results in a frequency of 250 KHz – but it’s probably not advisable especially if you need 10 bits of resolution. I have seen it in Arduino code – once – in the source code for an Arduino-based oscilloscope.

As I run the odd occasional NormDuino board at 8 MHz – see Appendix H – I can use either of the preceding two settings and possibly also the divide by 32 option, 101binary.

Assuming you are running an Arduino of some kind and are bypassing analogRead(), the following code would set the desired frequency:
ADCSRA = ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0));

The preceding code overwrites all settings in the ADCSRA register. If you are compiling in the Arduino IDE, this is a good idea as it will overwrite the Arduino’s default settings.

Regardless of the IDE, doing this means that you know exactly where you start in the ADC setup. All further settings in ADCSRA can be ORd or ANDed in the usual manner. The examples that follow will all begin by clearing the appropriate register on its first use and adding in additional requirements on all subsequent uses.

9.2.1.3 Selecting the Reference Voltage Source

There are three separate and selectable voltage references which can be used by the ADC, although only one can be selected at any one time.

The data sheet advises against selecting either of the two internal options if there is an external voltage already applied to the AREF pin. Doing this will most likely brick your ATmega328P. It’s best to check if your particular device has anything connected before changing the reference voltage source.

I’ve looked at the schematics for the Uno and the Duemilanove, and neither of those connects the AREF pin to any voltage. NormDuino also does not have any voltages on that pin.

The same cannot be said for a number of breadboard Arduino layouts to be found on the Internet, where they connect AVCC to VCC as required, but for some reason, also connect AREF to VCC, thus creating a time bomb, just waiting to happen and for no good reason. Beware.

Even the Arduino Language delays setting the analogReference() until the time that analogRead() actually executes and the source comes with a warning against having voltage applied to AREF.

The reference voltage is set by bits REFS1 and REFS0 in the ADC Multiplexer Selection Register, also known as ADMUX. The permitted values for these two bits are shown in Table 9-3.
Table 9-3

ADC reference voltage selection settings

REFS1–REFS0

Description

00

Use the AREF pin as the reference voltage. The external voltage applied must not exceed VCC

01

Use the (internal) voltage on AVcc as the reference voltage. For best results, there should be a 100 nF capacitor between AREF and GND

10

Reserved

11

Use the internal 1.1 V bandgap voltage as the reference. Again, it’s best to have a 100 nF capacitor between AREF and GND.

In the following example, we set the ADMUX register with an initial value for the reference voltage source, selecting the AVCC voltage, and will add to it as we progress:
ADMUX = ((0 << REFS1) | (1 << REFS0));

9.2.1.4 Left or Right Alignment?

The result of an ADC conversion is a value between 0 and 1023. This represents the voltage on the ADC input pin – see in the following – as compared with the reference voltage, both with respect to GND.

There are two registers that must be read to obtain the result, ADCH and ADCL. ADCH holds the highest bits of the result, while ADCL holds the lowest bits. Reading these registers must be done in a specific order – ADCL must be read first and then ADCH.

Once you have read ADCL, the ADC is no longer permitted to write to either ADCL or ADCH until after you have completed reading ADCH. This blocking method ensures that when you read the result of a conversion, both registers are giving you the appropriate bits of the same conversion result.

The ADC generates a 10-bit result, 0–1023, which is returned in the ADC data registers ADCH and ADCL. By default, the result is presented right adjusted, but can optionally be presented left adjusted by setting the ADLAR bit in ADMUX to a 1binary:
ADMUX |= (1 << ADLAR);
The default is for the result to be right aligned, which you can ensure by
ADMUX &= ~(1 << ADLAR);

The data sheet states that If the result is left aligned and no more than 8-bit precision is required, it is sufficient to read ADCH.

So what is the difference? The default, right alignment, returns ADCL with bits 9–8 of the result in bits 1–0 of ADCH and bits 7–0 of the result in bits 7–0 of ADCL.

In left alignment, bits 9–2 are in bits 7–0 of ADCH, and bits 1–0 of the result are in bits 7–6 of ADCL.

In Table 9-4, "x" means we don’t care about this bit of the result as it is outside the 10-bit resolution of the ADC.
Table 9-4

ADC left/right alignment options

ADLAR

Alignment

Result ADCH

Result ADCL

0

Right

xxxxxx98

76543210

1

Left

98765432

10xxxxxx

In code, you can read ADCW to get the correct result and not worry about reading ADCH and ADCL in the correct order.

9.2.1.5 Selecting an Input Source

It is now time to select an input source. This is the pin that will receive the voltage that we are comparing against the reference voltage source. There are 4 bits in register ADMUX, MUX3MUX0, which are used to select the input source. This gives up to 16 different sources, all of which are listed in Table 9-5; however, a number of the options are reserved and should not be used. ADC8, while listed in the data sheet as one of the reserved values, is actually an exception in that it can be safely used.
Table 9-5

ADC input voltage source settings

MUX3-MUX0

Description

MUX3-MUX0

Description

0000

ADC0 (Arduino A0, AVR PC0)

1000

Reserved

0001

ADC1 (Arduino A1, AVR PC1)

1001

Reserved

0010

ADC2 (Arduino A2, AVR PC2)

1010

Reserved

0011

ADC3 (Arduino A3, AVR PC3)

1011

Reserved

0100

ADC4 (Arduino A4, AVR PC4)

1100

Reserved

0101

ADC5 (Arduino A5, AVR PC5)

1101

Reserved

0110

ADC6 (Arduino A6, AVR ADC6)

1110

1.1 V internal bandgap

0111

ADC7 (Arduino A7, AVR ADC7)

1111

0 V (GND)

The data sheet has the following warnings:
  • If ADC3, ADC2, ADC1 or ADC0 are used not as ADC inputs, but as Digital Outputs, then they _must not switch while an ADC conversion is in progress._

  • If ADC4 or ADC5 are being used for 2WI (2 Wire Interface) purposes, then using that will affect only ADC4 and ADC5, not the other ADC inputs.

Now, the final two entries in Table 9-5 are interesting perhaps? I can only assume that they are there to enable some form of configuration perhaps. If you set MUX3:0 to 1110binary, then the ADC always reads 227–229 (at least mine does) which works out at 1.11–1.12 V. Using 1111binary for MUX3:0 returns zero on my devices, representing GND. Working on the assumption that this is indeed some form of configuration test, my ADC seems to be quite accurate – assuming, of course, that the 1.1 V bandgap reference voltage is itself 1.1 V of course.

If your code decides to change the ADC input channel while a conversion is underway and has not yet completed, nothing will happen until the current conversion finishes.

9.2.1.6 Disable Digital Input

When using a pin as an ADC input, you are required to disable its digital input buffer by setting the appropriate bit for the pin, in the Digital Input Disable Register 0 or DIDR0.

Only the pins corresponding to ADC0 (Arduino A0) through ADC5 (Arduino A5) have the ability to have their digital input buffers disabled. Pins ADC6 and ADC7, the two new ones on surface mount versions of the ATmega328, and ADC8 do not have digital input buffers, so you cannot have them disabled.

To disable the digital input buffer for a pin, you must write a 1binary to the appropriate ADCnD bit of the DIDR0 register, where the "n" represents the ADCn pin number. To disable the digital input buffer for pin ADC3, Arduino pin A3, for example, you would code
DIDR0 |= (1 << ADC3D);
Don’t forget to re-enable the buffer after the pin’s use as an ADC input is finished; otherwise, it will always read LOW. This is done by writing a 0binary to the appropriate bit in DIDR0, as follows:
DIDR0 &= ~(1 << ADC3D);

9.2.1.7 ADC Interrupt

There is a single interrupt attached to the ADC, the ADC interrupt, or ADC Conversion Complete interrupt, accessed through ISR(ADC_vect) in your code. This interrupt is enabled by setting the ADIE, ADC Interrupt Enable, bit in register ADCSRA as follows:
ADCSRA |= (1 << ADIE);
If global interrupts are also enabled, then the interrupt will fire every time that the ADC has completed a conversion and the result is available. Any time that the ADC conversion is complete, the ADIF bit in ADCSRA will be set and will remain set until either
  • The interrupt handler executes, whereupon ADIF will be automatically cleared.

  • The code writes a 1binary to ADIF in the usual AVR manner.

If your code is not using the ADC interrupt, it should monitor ADIF, and remember to clear it; otherwise, a further ADC conversion will not begin – unless the ADC is in free running mode, as described in the following.

The data sheet warns that we should beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled. This also applies if the SBI and CBI instructions are used.

9.2.1.8 Single-Shot or Auto Trigger?

In single-shot mode, a single conversion is carried out, and the ADC stops working until the next request for a conversion. The conversion is started on demand by the code, and the ADC will make the reading and then stop.

In auto trigger mode, the conversion is triggered by an event or can be put in free running mode which causes the ADC to continually make a new conversion as soon as the previous one has completed. This mode usually requires the ADC interrupt to be enabled to advise the code that a conversion has finished and that the result is available. Free running mode still requires the first conversion to be manually started, as described in the following.

The various triggers available are set up in register ADCSRB by setting bits ADTS2, ADTS1, and ADTS0 as per Table 9-6.
Table 9-6

ADC auto trigger sources

ADTS2–ADTS0

Trigger Source

000

Free running mode

001

Analogue Comparator

010

External Interrupt Request 0

011

Timer/counter 0 Compare Match A

100

Timer/counter 0 Overflow

101

Timer/counter 1 Compare Match B

110

Timer/counter 1 Overflow

111

Timer/counter 1 Input Capture Event

These bits are only used if the ADATE bit in register ADCSRA is also set. To set the ADC into auto trigger mode with free running, the code required would be
ADCSRA |= (1 << ADATE);
ADCSRB = ((0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0));
Register ADCSRB also contains, in bit 6, the ACME bit which is used by the Analogue Comparator – see Section 9.1, “The Analogue Comparator.” Setting ADCSRB in the preceding manner will clear that bit which might affect the running of the comparator if your device needs it. In that case, the preceding code should probably be changed to preserve the ACME bit before ORing the desired auto trigger bits:
ADCSRA |= (1 << ADATE);
ADCSRB &= (1 << ACME);
ADCSRB |= ((0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0));

And yes, I know ORing with zero has no effect, but it will have an effect for other auto trigger sources.

Auto triggering is initiated when a positive edge occurs on the selected trigger signal. When this occurs, the ADC’s prescaler is reset, and a new conversion is started. If the triggering signal is still positive when the current conversion finishes, a new conversion will not be automatically started.

Additionally, if another triggering positive edge is detected during an ADC conversion, the new triggering edge will be ignored.

The various Timer/counter 0– and Timer/counter 1–related auto triggering sources can be used to cause an ADC conversion to be initiated at regular intervals.

Even when auto triggering is enabled, your code can still manually request a single-shot conversion by initiating the conversion as described in the following.

9.2.1.9 Enabling the ADC and Initiating Conversions

Everything is now configured. All that remains is to enable the ADC and, if necessary, initiate the first conversion. The ADC is enabled by writing a 1binary to the ADEN bit in register ADCSRA, as follows:
ADCSRA |= (1 << ADEN);
When ADEN is set as in the preceding text, the following events occur:
  • The ADC starts consuming power.

  • The ADC prescaler starts counting.

  • If configured, auto triggering events will now initiate an ADC conversion.

An ADC conversion is manually requested, in single-shot or free running mode, by writing a 1binary to the ADSC bit in register ADCSRA:
ADCSRA |= (1 << ADSC);
When the ADSC bit is set, the following events occur:
  • The ADC prescaler is reset so that each conversion takes the same time.

  • The sample and hold circuitry charges its capacitor with the voltage on the ADC input pin.

  • The chosen reference voltage is enabled.

  • The input channel selection is made, and the appropriate input is connected to the ADC.

  • The conversion then starts at the next rising edge of the ADC clock.

9.2.1.10 ADC Conversions

The very first conversion takes 25 ADC clock cycles to complete so that the internal analogue circuitry can be initialized. Subsequent conversions take only 13 ADC clock cycles.

If the ADC’s reference voltage is the internal bandgap voltage, then it will take a certain time for the voltage to stabilize. If it is not stabilized, the first conversion’s result may be wrong. The data sheet, sadly, does not specify how long a certain time should be. My own code simply throws away the first reading in that mode of operation, although I have seen calls to delay(20) in some code as a suitable delay to allow the stabilization to occur.

The sample and hold of the input voltage takes place after 13.5 ADC clock cycles for the first conversion and after only 1.5 ADC clock cycles after the start of subsequent conversions.

When an ADC conversion is complete, the result is written to the data registers ADCH and ADCL, and then bit ADIF in ADCSRA is set. When running in single-shot mode, ADSC in ADCSRA is cleared simultaneously with the setting of ADIF.

If the code then sets bit ADSC to 1binary, a new conversion will be initiated on the first rising edge of the ADC clock signal.

In any of the auto triggering modes, the ADC prescaler is reset as soon as the triggering event occurs. This ensures a fixed delay from the triggering event occurring to the start of a new ADC conversion. In this mode, the sample and hold takes place two ADC clock cycles after the rising edge on the trigger source signal. An additional three CPU clock cycles, not ADC clock cycles, are used for synchronization logic.

In free running mode, a new conversion will start as soon as the previous one completes, and this will occur even if the ADIF flag in the ADCSRA register is not cleared.

9.2.2 Noise Reduction

There is a sleep mode especially for the ADC. It disables many of the internal clocks leaving the ADC to make its conversion with as few noise sources internally as possible. This sleep mode is discussed in Chapter 7, Section 7.3.9.2, “ADC Noise Reduction Sleep Mode.”

It should be noted that this Noise Reduction mode is available only in single-shot ADC mode. The data sheet specifies the following about enabling this sleep mode:

The following procedures should be used:
  • Make sure that the ADC is enabled and is not busy converting.

  • Single Conversion mode must be selected and the ADC conversion complete interrupt must be enabled.

  • Enter ADC Noise Reduction mode (or Idle mode). The ADC will start a conversion once the CPU has been halted.

  • If no other interrupts occur before the ADC conversion completes, the ADC interrupt will wake up the CPU and execute the ADC Conversion Complete interrupt routine. If another interrupt wakes up the CPU before the ADC conversion is complete, that interrupt will be executed, and an ADC Conversion Complete interrupt request will be generated when the ADC conversion completes.

  • The CPU will remain in active mode until a new sleep command is executed.

It also gives the following point to note.

The ADC will not be automatically turned off when entering other sleep modes than Idle mode and ADC Noise Reduction mode. The user is advised to write zero to ADEN before entering such sleep modes to avoid excessive power consumption.

9.2.3 Temperature Measurement

See Appendix E for an example of using this facility of the ADC. To select this ADC input, your code needs to
  • Select the internal 1.1 V bandgap as the ADC reference voltage (REFS1:0 = 11binary).

  • Select ADC8 as the ADC input channel (MUX3:0 = 1000binary).

  • Set the prescaler – divide by 128 on an Arduino board at 16 MHz (ADPS2:0 = 111binary).

  • Execute single conversions as and when required. Auto triggering is not permitted.

The data sheet advises that readings from an uncalibrated sensor are
  • -40oC = 010Dhex (269decimal)

  • 25oC = 0160hex (352decimal)

  • 125oC = 01E0hex (480decimal)

This works out at approximately 1.2769 per degree C, and that matches with the data sheet which states that it is approximately 1 LSB [degrees Kelvin] or 1 degree C. The data sheet states that the temperature in degrees Centigrade is calculated as
Temp_C = (((((ADCH << 8) + ADCL)
       - (273 + 100 - TS_OFFSET)) * 128) / TS_GAIN)
       + 25
Given the rules of arithmetic precedence, the preceding code would cause ADCH to be read before ADCL, and this would not be the correct order! Using ADCW instead of ((ADCH << 8) + ADCL) would give the correct result and cause the two registers to be read in the correct order.
  • TS_OFFSET is the calibration offset for the sensor and is stored in the device itself. It is a signed two’s compliment value.

  • TS_GAIN is the sensor gain factor and is also stored in the device. It is an unsigned, fixed-point, 8-bit value representing 1/128th units, hence the need to multiply by 128 in the preceding text.

In the data sheet, there is a small assembly language routine to obtain both TS_OFFSET and TS_GAIN from the device. I do not use that particular method of temperature conversion, so I have not discussed that routine here.

There are, over the Internet, various methods of converting the temperature from what the ADC reads to degrees Centigrade. Some I have seen are as follows:
  • ADC - an_offset – The offset is dependent on the individual AVR device.

  • (ADC - 247)/1.22 – From the developer help note mentioned earlier and linked in the following.

  • (((ADC - (273 - 100 - TS_OFFSET)) * 128) / TS_GAIN) + 25 – From the data sheet itself.

  • ADC – 273 – From the application note on calibrating the sensor, linked in the following.

  • (ADC - an_offset) a_gain_factor – From the "MySensors" code, linked in the following.

The Atmel/Microchip documents mentioned in the preceding text are as follows:
Interestingly, the calibration document mentioned in the preceding text states something a little different from the information in the data sheet, in that
  • The output from the ADC is given in LSBs or K, so the calibration values ADCT1 and ADCT2 have to be converted to degrees C. This is done by subtracting 273 from the values

This, to my mind, implies that the temperature sensor is outputting a value representing degrees K (Kelvin) whereby 0 degrees Centigrade is 273 degrees Kelvin, hence the need to subtract 273 from the ADC reading, to get a value for Centigrade. In practice, this does not compute!

9.2.4 ADC Example

The following code initializes the ADC in free running mode and uses an interrupt to send the ADC reading to the Serial Monitor. The code was written and compiled in the Arduino IDE, but uses the plain AVR C language to set up the ADC.

This code can be run on my Uno or Duemilanove at 16MHz or on one of my 8 MHz NormDuino boards. The prescaler for the AVR is calculated based on the F_CPU chosen in the Arduino IDE, and the code correctly determines whether the board is 16 MHz or 8 MHz and sets the correct prescaler accordingly.

Figure 9-2 shows the breadboard layout where I simply connected a potentiometer to pin A0 to vary the voltage. An LED on pin D9 with a 560 Ohm resistor gave some visual feedback as it brightened and dimmed according to where I had turned the potentiometer.
Figure 9-2

ADC example sketch breadboard layout

The first part of the source code is shown in Listing 9-2 and is the function setupADC() which sets up the ADC directly. As mentioned earlier, it will be set up in free running auto trigger mode and will use the ADC interrupt to pass the readings to the main loop as each one becomes available.
void setupADC() {
   // Ensure ADC is powered.
   PRR &= ~(1 << PRADC);                                  ①
   // Slow the ADC clock down to 125 KHz
   // by dividing by 128 or 64. 128 is for a 16MHz Arduino
   // 64 for an 8MHz NormDuino. Does not cater for other
   // clock speeds here. BEWARE.
  #if F_CPU == 16000000                                   ②
   ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
  #else
   // Non-standard 8MHz clock in use.
   ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
  #endif
   // Initialise the ADC to use the
   // internal AVCC 5V reference voltage.
   ADMUX = (0 << REFS1) | (1 << REFS0);                   ③
   // Ensure result is right aligned.
   ADMUX &= ~(1 << ADLAR);
   // Use the ADC multiplexer input
   // ADC0 = Arduino pin A0.
   ADMUX |= (0 << MUX3) | (0 << MUX2) |                  ④
           (0 << MUX1) | (0 << MUX0);
   // Disable ADC0 Digital input buffer.
   DIDR0 |= (1 << ADC0D);
   // Use the interrupt to advise when a result is available.
   ADCSRA |= (1 << ADIE);                                ⑤
   // Set auto-trigger on, and choose Free Running mode. As
   // we are not using the Analogue Comparator, we don't care
   // about the ACME bit in ADCSRB.
   ADCSRA |= (1 << ADATE);                               ⑥
   ADCSRB = 0;
   // Enable the ADC and wait for the voltages to settle.
   ADCSRA |= (1 << ADEN);                                ⑦
   delay(20);
}
Listing 9-2

ADC example, setupADC() function

  •      ①     This powers the ADC by disabling the disable the ADC bit.

  •      ②     These lines work out the prescaler for 16 MHz or 8 MHz devices. Other speeds are not catered for here. I only have two speeds on my devices.

  •      ③     This selects the internal 5 V reference voltage which is fed by pin AVCC which must be connected to VCC plus or minus 2%.

  •      ④     I know it’s all zeros, but it’s easy to change this line for different ADC input pins.

  •      ⑤     We are using interrupts, so they must be enabled, as must the global interrupts. This is always the case when compiling in the Arduino IDE, but not necessarily in other IDEs. Beware.

  •      ⑥     Setting ADATE enables auto trigger mode. Setting ADCSRB to zero enables free running mode. It also messes up the Analogue Comparator, but we don’t care here. Other code might care, so bear it in mind.

  •      ⑦     Enable, but do not start the ADC. From here on, the ADC draws power and has initialized the reference voltage selector and the reference voltage source. It is ready to go.

After executing the code in Listing 9-2, the ADC is now fully initialized and enabled; however, it has not yet been started. In free running and single-shot modes, the ADC must be started manually. The startADC() function in Listing 9-3 does exactly that by setting bit ADSC in ADCSRA.
void startADC() {
    ADCSRA |= (1 << ADSC);
}
Listing 9-3

ADC example, startADC() function

Listing 9-4 sets up a volatile variable, ADCReading, to hold the result passed back from the interrupt handler. The variable must be defined as volatile; otherwise, the compiler might notice that it doesn’t seem to be being changed in value and may simply "optimize" it away. Any global variables that you wish to change from inside an interrupt handler should be defined as volatile to prevent this happening.

Following the variable declaration, we need an interrupt handler for the ADC interrupt. All that it needs to do is to copy the ADC result from ADCW into ADCReading. The loop() function , in Listing 9-6, will do some work with this result.
// Somewhere for the ADC Interrupt to store the result.
volatile uint16_t ADCReading = 0;
// The interrupt handler.
ISR(ADC_vect) {
    ADCReading = ADCW;
}
Listing 9-4

ADC example, the interrupt handler

The setup() function, in Listing 9-5, initializes the ADC using the setupADC() function from Listing 9-4. This sets up the Serial Monitor to display the results and fires up the ADC for its first reading. Once the first reading is complete, the ADC will then be in free running mode and will constantly be initiating a new conversion as soon as the current one finishes.
void setup() {
    setupADC();
    // Use the Serial monitor for output.
    Serial.begin(9600);
    Serial.println("Arduino Direct ADC Testing");
    // Add an LED and 560R resistor to pin 9 for feedback.
    pinMode(9, OUTPUT);
    // Now, fire up the ADC.
    startADC();
}
Listing 9-5

ADC example, the setup() function

And finally, Listing 9-6 shows the loop() function, which takes the most recent ADC reading and sends it to the Serial Monitor, first as a plain value between 0 and 1023 and, second, as a voltage.

As there are 1024 different values that can be returned from the ADC and with 0 = GND and 1023 = AVCC or 5 V, anything in between must be equal to 5/1024 per division. We simply multiply ADCReading by this fraction, 0.004882812 V (4.88 milliVolts and a little bit), to get the voltage on the ADC0 or A0 pin. We must be careful to cast the result to a float, or we will lose accuracy and only see integer values.
void loop() {
    Serial.print("ADC = ");
    Serial.print(ADCReading);
    // The voltage is ADCReading * (5V/1024)
    Serial.print(", Voltage = ");
    Serial.println((float)(ADCReading * 5.0 / 1024.0));
    // Light up the LED to a value representing the voltage
    // on pin A0 (ADC0).
    analogWrite(9, map(ADCReading, 0, 1023, 0, 255));
    delay(500);
}
Listing 9-6

ADC example, the loop() function

There’s a small delay at the end of the loop() to prevent the numbers scrolling up the screen too quickly. Don’t worry about the ADC though. It will carry on taking readings and passing them back to the loop() as the interrupt handler works outside of delay() and is not affected.

9.3 USART

The USART is the Universal Synchronous/Asynchronous Receiver/Transmitter. It’s easier to type USART!

On the Arduino, it is connected from physical pins 2 and 3 to the laptop or desktop’s USB port via either a separate Atmel/Microchip AVR microcontroller or an FTDI chip – depending on which Arduino you have. On an ordinary ATmega328P, it is also pins 2 and 3, but they are not connected anywhere – unless you connect them.

Pin 2 is the receive or RX pin, while pin 3 is the transmit or TX pin. You don’t have to worry about this on an Arduino, but when you have your own naked ATmega328P on a breadboard or PCB and you want to communicate with it, you do. With a serial device such as an FTDI connector, at least the one I have, the pins are marked TXO and RXO for output. So the TXO pin on the FTDI connects to the ATmega328P’s RX pin, and the RXO on the FTDI connects to TX on the AVR.

Confused? You should be! Some FTDI devices have the pins marked as TX and RX, and with those, you connect the TX to the TX and the RX to RX. It will not break anything if you get them crossed over, but nothing will work. If that happens to you, just swap the wires over at the AVR end.

The USART can be set up to transmit only, receive only, or do both; and it can use interrupts to facilitate this, having three dedicated interrupts available.

In synchronous modes, the USART will require a clock pin and a data pin, whereas asynchronous mode does not need a clock and can use one pin, of the two available to the USART, as the TX pin and the other for the RX pin. The Arduino uses the latter mode, asynchronous.

9.3.1 Baud Rates

The baud rate is configured by the value stored in the USART Baud Rate Register, UBRR0, where "0" is the USART number. On the ATmega328P, there is a single USART numbered 0. The Mega 2560 has four USARTs, numbered from 0 to 3.

UBRR0 is connected to a counter which counts down at the F_CPU frequency, and whenever it reaches zero, a USART clock is generated and this clock controls the USART’s transmission and/or receipt of data. When the counter reaches zero, it is reloaded with the value in UBRR0. This clock is the Baud Rate Generator Clock and has the frequency given by
F_CPU / (UBRR0 + 1)

The Baud Rate Generator Clock is divided down by the USART’s transmitter by 2, 8, or 16 depending on the configured mode.

The receiver circuitry, on the other hand, does no such division and uses the Baud Rate Generator Clock directly as input to its data recovery unit. Within the data recovery unit, there is a state machine which uses 2, 8, or 16 states to determine correct receipt of the transmitted data.

9.3.2 Double Speed

The transfer rate of the USART can be doubled by setting bit U2X0 in register UCSR0A. However, this bit should only be set in asynchronous operation; it should be zero for synchronous modes. Setting the bit causes the baud rate divider to be reduced to 8, from the usual 16, and this effectively doubles the transfer rate.

The receiver, however, will also only sample eight times, rather than 16, in the data recovery unit, thus doubling the receive speed too, but it should be noted that in this mode, a more accurate baud rate setting is required as well as a more accurate system clock. Some baud rates can cause excessive error rates – see the following for details.

For the transmission side of the USART, doubling the speed has no apparent drawbacks. (At least, that’s what the data sheet says.)

9.3.3 Baud Rate Calculations

In single-speed asynchronous mode, the baud rate is calculated as
BAUD = F_CPU / 16 * (UBRR0 + 1)
Normally, however, you are more interested in setting a specific baud rate, so you would need to calculate UBRR0 for the desired rate. This is done using the formula
UBRR0 = (F_CPU / (16 * BAUD)) - 1

You will probably have figured out that you can just about define any baud rate you wish by setting UBRR0 to any given value.

In double-speed asynchronous mode, the formulas change to
BAUD = F_CPU / 8 * (UBRR0 + 1)
and
UBRR0 = (F_CPU / (8 * BAUD)) - 1
In synchronous (master) mode, which incidentally the Arduino cannot use, the formulas again change to
BAUD = F_CPU / 2 * (UBRR0 + 1)
and
UBRR0 = (F_CPU / (2 * BAUD)) - 1
As an example, what would the required UBRR0 setting be for an Arduino running at 16 MHz, for a baud rate of 9600 in single-speed mode?
UBRR0 = (F_CPU / 16 * BAUD) - 1
      = (16e6 / 16 * 9600) - 1
      = (16e6 / 153,600) - 1
      = 104.1666... - 1
      = 103.1666...
See the problem? We are working with registers, and registers cannot have fractions, so the preceding calculation introduces errors. Do we use the value 103 and round down or use 104 by rounding up? What are the actual baud rates obtained with those values? If we feed them back into the equation, we will see, first for UBRR0 = 103
BAUD = F_CPU / 16 * (UBRR0 + 1)
     = 16e6 / 16 * (103 + 1)
     = 16e6 / 16 * 104
     = 16e6 / 1664
     = 9615.384615
and now for UBRR0 = 104
BAUD = F_CPU / 16 * (UBRR0 + 1)
     = 16e6 / 16 * (104 + 1)
     = 16e6 / 16 * 105
     = 16e6 / 1680
     = 9523.809524

Neither of these is 9600, which we wanted, so it looks like rounding down, in this case, works out closer to our desired baud rate.

9.3.4 Baud Rate Errors

With UBRR0 set to 103 and 104, as in the preceding text, we have actual baud rates of 9615 and 9524. My rounding here is in the normal direction: less than 0.5 rounds down, more than 0.5 rounds up, and 0.5 rounds up or down to the even number.

We wanted 9600, but we got 9615 or 9524. One is running high and the other low. Which has the lowest error rate?

The data sheet calculates the error rate, as a percentage, as
error% = ((BAUDgot / BAUDwanted) - 1) * 100

The data sheet supplies numerous tables showing the desired baud rates, the UBRR0 setting, and error rates for many different values of F_CPU; and they all appear to be wrong!

Taking the best value calculated in the preceding text and checking the data sheet, it shows the error rate as being 0.2% for UBRR0 having the value 103. I feel the need to run a quick check myself and calculate the error rate for UBRR0 = 103:
error% = ((BAUDgot / BAUDwanted) - 1) * 100
       = ((9615 / 9600) - 1 ) * 100
       = (1.0015625 - 1) * 100
       = 0.15625%
Hmm, that’s not quite the 0.2% that the data sheet mentions. So let’s try again but this time, use the fractions:
error% = ((BAUDgot / BAUDwanted) - 1) * 100
       = ((9615.384615 / 9600) - 1 ) * 100
       = (1.001602564 - 1) * 100
       = 0.1602%

That’s still not 0.2%. There are many other discrepancies in the data sheet on the matter. To be honest, I think the data sheet is rounding error rates to the nearest 0.1%.

Regardless of the data sheet’s approximate error rates, the advice given is to choose a baud rate which gives an error rate of between –0.5% and +0.5%. On an Arduino board, running at 16 MHz, a 9600 baud rate is within specifications. Using this advice, it would seem that UBRR0 can safely be set to 103 as calculated.

9.3.5 What Is a Frame?

According to the data sheet, A serial frame is defined to be one character of data bits with synchronization bits (start and stop bits), and optionally a parity bit for error checking.

Start and stop bits are used to synchronize the transmitting and receiving devices, while parity bits are used to apply rudimentary error checking.

The USART is able to be configured to use any combination of the following:
  • 1 start bit

  • 5, 6, 7, 8, or 9 data bits

  • None, even, or odd parity

  • 1 or 2 stop bits

A frame always begins with the single start bit. This is followed by 5, 6, 7, 8, or 9 data bits with the least significant bit first. If parity is enabled, the parity bit is next. Finally are the 1 or 2 stop bits.

When a frame has been transmitted, it can either be followed by another frame, or the line can be set to a HIGH for idle state.

It is because of the frame structure that regarding the baud rate as the number of characters per second is incorrect. For an 8-bit character set, a frame can be as much as 12 bits long.

9.3.6 Parity

The USART operates, or can be configured to do so, in odd or even parity or with no parity at all. When configured to use parity, the way it is calculated is to exclusively OR, or XOR, each of the bits in the data, not the bits in the frame, and then to XOR the result with a 0binary bit for even parity or a 1binary bit for odd parity.

An exclusive OR operation takes 2 bits, and if they are both the same, the result is 0binary. If they are different, the result is 1binary, giving the following truth table:
A B | Z
----+--
0 0 | 0
0 1 | 1
1 0 | 1
1 1 | 0

If even parity is in use, the parity bit is used to make the number of 1binary bits in the data even. Odd parity makes the number of 1 bits odd. The parity bit will be found between the final data bit and the first stop bit of a frame.

As an example, the letter "A," in ASCII, has code 65decimal, 41hex, or 0100 0001binary. There are 2 bits that are 1binary. So for even parity, the parity bit must be a 0binary; and for odd parity, it must be a 1binary.

The letter "C," on the other hand, is 67decimal, 43hex, and 0100 0011binary. This has 3 bits that are 1 binary. So the parity bit in even parity will be a 1binary, and for odd parity, it will be a 0binary.

9.3.7 Interrupts

The USART has three separate interrupts that can be used. Two are for transmission and one for receiving data. These are
  • TX Complete

  • TX Data Register Empty

  • RX Complete

9.3.7.1 TX Complete Interrupt

This interrupt fires when the data written to the UDR0 register has been framed in start, stop, and parity bits as appropriate and the whole of the frame has been transmitted.

9.3.7.2 TX Data Register Empty Interrupt

This interrupt fires when the data written to the UDR0 register has been written into the shift register buffer internally, to be framed. The USART may still be in the midst of sending the byte down the line, but the UDR0 register is empty and another byte can be written.

This is the interrupt used by the Arduino’s Serial interface and allows for a slightly quicker processing of data to be written to the USART as it can be written into the UDR0 register even as the previous byte is still being wrapped and transmitted in its frame.

9.3.7.3 RX Complete

When this interrupt fires, a new data byte is waiting to be retrieved from the UDR0 register.

It appears that the UDR0 register is used by both the transmit and receive parts of the USART. How can this be when the USART can be both transmitting and receiving? When data are read from UDR0, the byte of data recently received is returned to the calling code, while when data are written to UDR0, it is forwarded on to the transmitter side of things. The ATmega328P knows what it is doing!

9.3.8 Initializing the USART

It goes without saying that the USART will have to be initialized before any communication can take place. The process usually requires the USART to be powered up, choose the USART mode, set the baud rate, set the frame requirements, and then enable the transmitter and/or the receiver depending on the requirements of the code.

When running code with interrupt-driven USART operations, the global interrupts should be disabled during the setup.

If the USART needs to be reconfigured, perhaps to change the frame format or the baud rate, then the code must ensure that the existing settings have been finished with and that all current transmissions and receipts are completed. This can be carried out by checking the TXC0 and/or RXC0 bits in the UCSR0A register.

The USART has three separate control registers:
  • UCSR0A is used for various error flags and transmit and receive complete flags and to set double-speed mode and multi-processor communications mode.

  • UCSR0B is used to enable interrupts, to enable transmit and receive modes, and to hold bit 9 of 9-bit data frames, for transmission and receipt and one of the 3 bits used to set the data size – the other two are in UCSR0C.

  • UCSR0C is used to select the USART mode, parity settings, stop bits, the remaining 2 bits of the data size settings, and the clock polarity.

In order to ensure that your USART is fully initialized, without relying on some defaults that may not be as expected, it is best to start from a known, clear configuration by clearing all three control registers:
UCSR0A = 0;
UCSR0B = 0;
UCSR0B = 0;

Any options that the code requires can now be safely ORd into the appropriate registers. The following examples will assume this mode of operation.

9.3.8.1 Powering the USART

The USART is powered up by writing a 0binary to the PRUSART0 bit in the Power Reduction Register, PRR, as described in Chapter 7, Section 7.4.2, “Power Reduction Register”:
PRR &= ~(1 << PRUSART0);

Writing a 1binary to this bit shuts down the USART by stopping the clock to the module. When subsequently waking the USART again, it should be fully re-initialized to ensure proper operation.

9.3.8.2 Choosing the USART Mode

The USART can be operated in one of three modes:
  • Asynchronous USART (the default)

  • Synchronous USART

  • Master SPI

The default mode is asynchronous USART, and the desired mode is defined by setting the UMSEL01 and UMSEL00 bits in the UCSR0C register, USART Control Status Register C, as defined in Table 9-7.
Table 9-7

USART mode settings

UMSEL01–UMSEL00

Description

00

Asynchronous USART

01

Synchronous USART

10

Reserved – do not use

11

Master SPI

Synchronous mode(s) requires a clock and a data line, where asynchronous mode does not. There are two pins available to the USART, so because asynchronous mode doesn’t use a clock, the two pins can be configured as TX and RX.

In this mode, transmission and reception can occur at the same time – also known as full duplex.

Arduino boards run in asynchronous mode.

As an example, setting the USASRT to run in Master SPI mode, your code would be
UCSR0C |= ((1 << UMSEL01) | (1 << UMSEL00));

9.3.8.3 Baud Rate Setting

Setting the baud rate has been described earlier. You must calculate the appropriate value for the UBRR0 register and load it, for example:
// Settings for 9600 baud rate.
#define BAUD 9600
#define UBRR0_9600 ((F_CPU) / 16 * (BAUD)) - 1
...
UBRR0 = UBRR0_9600;

9.3.8.4 Frame Settings

The frame settings define the start bit, which is always present, the number of data bits to be transmitted/received, whether or not a parity bit is required, and the number of stop bits.

9.3.8.5 Setting Parity

Bits UPM01 and UPM00 in the UCSR0C register, USART Control Status Register C, define the USART parity mode. Table 9-8 shows the bit settings for the different parity modes.
Table 9-8

USART parity settings

UPM01–UPM00

Description

00

No parity (the default)

01

Reserved – do not use

10

Even parity

11

Odd parity

To configure the USART with even parity, the code would be
UCSR0C |= ((1 << UPM01) | (0 << UPM00));

9.3.8.6 Setting Stop Bits

The USBS0 bit in the UCSR0C register defines the number of stop bits as shown in Table 9-9.
Table 9-9

USART stop bit settings

USBS0

Description

0

1 stop bit (the default)

1

2 stop bits

The code to configure the USART with 2 stop bits would therefore be
UCSR0C |= ((1 << USBS0);

9.3.8.7 Setting Data Width

Bits UCSZ01 and UCSZ00 in register UCSR0C, along with bit UCSZ02 in register UCSR0B, define the number of data bits in a frame. The default, at power on/reset, is 8 bits. Table 9-10 shows the valid settings for the data width which the USART will use. You will note some settings are not permitted.
Table 9-10

USART data width settings

UCSZ02–UCSZ00

Description

000

5 bits data size

001

6 bits data size

010

7 bits data size

011

8 bits data size (the default)

100

Reserved – do not use

101

Reserved – do not use

110

Reserved – do not use

111

9 bits data size

To set, for example, 9 bits of data in the frame, your code should execute the following:
UCSR0B |= (1 << UCSZ02);
UCSR0C |= ((1 << UCSZ01) | (1 << UCSZ01));

9.3.8.8 Enabling Double-Speed Mode

To double the speed of communications, both transmission and receipt, in asynchronous mode only, set bit U2X0 in register UCSR0A as follows:
UCSR0A |= (1 << U2X0);
If the USART is to be operated in synchronous mode, this bit can be explicitly cleared, if required:
UCSR0A &= ~(1 << U2X0);

9.3.8.9 Enabling Interrupts

The USART has thee interrupts, as detailed earlier. Bits RCXCIE0, TXCIE0, and UDRIE0 control which, if any, of the interrupts will be used; and the settings are displayed in Table 9-11.
Table 9-11

USART interrupts

Bit

Description

RCXCIE0

RX Complete interrupt will be enabled. The code in ISR(USART_RXC_vect) will handle reading the UDR0 register to retrieve the byte just read

TXCIE0

TX Complete interrupt will be enabled. The code in ISR(USART_TXC_vect) will handle writing a new data byte to the UDR0 register ready to be transmitted

UDRIE0

USART Data Register Empty interrupt will be enabled. The code in ISR(USART_UDRE_vect) will handle writing a new data byte to the UDR0 register ready to be transmitted

The latter two interrupts appear do the same thing. They do, but slightly differently.

The TX Complete interrupt is fired when the data frame of up to 13 bits has been transmitted. At this point, any new data written to UDR0 has to be framed before it can be transmitted.

The USART Data Register Empty interrupt is fired whenever the date most recently written to UDR0 has been copied to the transmit shift buffer internally to the AVR microcontroller. There can be up to 9 bits of data, and so this interrupt can fire when the previous character is still in the midst of being framed and transmitted; and, in doing so, you can get a better throughput as the byte can be framed and transmitted as soon as the previous byte is on its way down the line.

The Arduino uses the USART Data Register Empty interrupt for better throughput.

Enabling these interrupts is a matter of writing a 1binary to the appropriate bit in the UCSR0B register:
cli();
...
UCSR0B |= ((1 << RCXCIE0) | (1 << UDRIE0));
...
sei();

If interrupts are to be used, it is considered best to disable global interrupts while initializing the USART. This will prevent spurious firing of the USART interrupts, possibly, when the USART is not fully configured.

9.3.8.10 Enabling Data Transmission

To enable transmission of data, the USART is configured as follows:
UCSR0B |= (1 << TXEN0);

Doing so will override the normal function of the TX pin on the AVR microcontroller. This corresponds to Arduino pin D1 or AVR pin PD1.

9.3.8.11 Enabling Data Receipt

To enable receipt of data, the USART is configured as follows:
UCSR0B |= (1 << RXEN0);

Doing so will override the normal function of the RX pin on the AVR microcontroller. This corresponds to Arduino pin D0 or AVR pin PD0.

9.3.8.12 Transmitting or Receiving 9-Bit Data

Nine-bit data? What’s that about? It can happen that some data transmissions require 9 bits for each character. The ATmega328P’s USART can cope with this. Given that data bytes in registers are only 8 bits long, where does the 9th bit get stored?

Bits TXB80 and RXB80 in register UCSRB0 are the places. These bits hold the 9th bit when transmitting and receiving 9-bit data. You should note that
  • When transmitting data, the 9th bit must be written to TXB80 before writing the remaining 8 bits to the UDR0 register.

  • When receiving data, the 9th bit must be read from RXB80 before reading the rest from UDR0.

In either case, the appropriate bit in register UCSRB0 holds the most significant bit of the 9-bit data. This will be bit number 8 – bits number from 0 upward remember. Data bits 7–0 will be in the UDR0 register when transmitting or receiving 9-bit data.

9.3.9 USART Checks

When all the initialization has been completed and the USART is now transmitting and/or receiving data quite happily, how do you check for completion or errors?

With interrupts in force, you will know when data are received and/or transmitted without problems as the appropriate interrupt will fire. However, if you are not using interrupts, you will have to poll various bits in the control registers, to see if data have been received or transmitted.

All of the bits to be checked or polled are found in register UCSR0A.

9.3.9.1 USART Receive Complete

Bit RXC0 in register UCSR0A is set when the current byte being received has been received and unframed. The data byte will be available for reading from the UDR0 register.

When the UDR0 register is subsequently read, RXC0 will be automatically cleared, as it will if the USART RX Complete interrupt is enabled and the ISR has been executed.

Code may also clear this bit by the usual manner of writing a 1binary to it.

9.3.9.2 USART Transmit Complete

Bit TXC0 in register UCSR0A is set when the frame in the transmit buffer (a simple shift register) has been completely shifted out onto the data line, and no new data is waiting in the UDR0 register for transmission.

This bit will be automatically cleared when the USART TX Complete interrupt handler has been executed or can be cleared in code by writing a 1binary to it.

9.3.9.3 USART Data Register Empty

Bit UDRE0 in register UCSR0A indicates that the register UDR0 is now empty, its previous contents having been copied into the transmit buffer ready for framing and transmission.

This bit will be automatically cleared when the USART Date Register Empty interrupt handler has been executed. Application code may also clear this bit by writing a 1binary to it.

On reset or power-up, this bit is initialized to a 1binary to show that the transmit data register is ready to accept new data.

9.3.9.4 USART Frame Error

Bit FE0 in register UCSR0A will be set if the received data byte had a framing error in that the first stop bit was detected as a 0binary. The bit remains set until UDR0 is read and so should be checked prior to reading the data.

When writing to register UCSR0A, this bit should always be written as 0binary.

9.3.9.5 USART Data Overrun

Bit DOR0 in register UCSR0A will be set whenever a Data Overrun condition is detected. A Data Overrun occurs when
  • The receive buffer is full – it can hold up to two characters.

  • There is a character waiting in the USART’s receive shift register to be copied into the receive buffer.

  • A new start bit is detected on the RX pin.

DOR0 will remain set until the receive buffer (UDR0) is read, freeing up space for new data.

When writing to register UCSR0A, this bit should always be written as 0binary.

9.3.9.6 USART Parity Error

Bit UPE0 in register UCSR0A will be set if the next character in the receive buffer had a parity error when received but only if the USART had parity checking enabled at the time the data was received (Bit UPM01 in register UCSR0C was set to 1binary).

UPE0 will remain set until the receive buffer (UDR0) is read.

When writing to register UCSR0A, this bit should always be written as 0binary.

9.3.10 USART Example

The code that follows in Listings 9-7 through 9-15 is a sketch to demonstrate the use of the USART without help from the Arduino Language and without using the Serial interface as we would normally do. The code that follows is all one sketch but is split into separate functions here for explanation.

The sketch begins with the setupUSART() function in Listing 9-7.
#define SET_UBRR0(x) ((F_CPU) / (16 * (x))) - 1        ①
void setupUSART(unsigned long baudRate) {
    // Sets up the USART to send and receive at a given baud,
    // 8 data bits, one stop bit, no parity. It doesn't use
    // interrupts or double speed.
    // Ensure we have power/clock.
    PRR &= ~(1 << PRUSART0);                          ②
    // calculate the baud rate setting.
    UBRR0 = SET_UBRR0(baudRate);                      ③
    // Initialise registers, then set TX and RX on,
    // 8 data bits. The others default appropriately.
    UCSR0A = UCSR0B = UCSR0C = 0;                     ④
    UCSR0B |= ((1 << TXEN0) | (1 << RXEN0));          ⑤
    UCSR0C |= ((1 << UCSZ01) | (1 << UCSZ00));        ⑥
}
Listing 9-7

USART sketch, setupUSART() function

  •      ①     This is a quick way to convert the desired baud rate into the required value for UBRR0.

  •      ②     Always remember to power up the USART first.

  •      ③     We set the required baud rate here.

  •      ④     Clearing all the control registers is a good way to set up the system to a known configuration.

  •      ⑤     This line enables transmission and receipt of data.

  •      ⑥     These 2 bits set the data size in the frame to 8.

You will note that much of the setup was not mentioned. Where, for example, did I put the USART into asynchronous mode? My initialization of the three control registers to zero did the following for me:
  • UCSR0A set double-speed and multi-processor modes off.

  • UCSR0B set interrupts off.

  • UCSR0C set the mode to asynchronous and defined no parity and a single stop bit.

All that the rest of setupUSART had to do was enable 8 bits of data frame size and turn on transmission and receipt of data.

The next function, shown in Listing 9-8, is the code to receive a single byte of data from the RX line. This code will block if there is nothing currently being received.
uint8_t receiveByte() {
    // Wait for bit RXC0 to be set in UCSR0A.
    loop_until_bit_is_set(UCSR0A, RXC0);       ①
    return UDR0;                               ②
}
Listing 9-8

USART sketch, receiveByte() function

  •      ①     Wait here until the RXC0 bit gets set. Once that happens, the data in the UDR0 register is valid.

  •      ②     Retrieve and return the character just read.

Receiving one byte at a time is okay, but sometimes you just want more! The next function, receiveText(), receives a whole string of characters. Listing 9-9 has the details.
uint8_t receiveText(char *buffer, uint8_t howMany) {
    // Receive a string of text up to howMany characters
    // or until a terminating linefeed is received.
    //
    // MAKE SURE that the serial monitor is set to send a
    // NEWLINE or this code will fail to return until the
    // buffer fills.
    //
    // Assumes the caller knows what s/he is doing! The
    // buffer should be one more than howMany in length.
    uint8_t i = 0;
    while (i < howMany) {               ①
        uint8_t c = receiveByte();
        if (c == '\n') {                ②
            buffer[i] = '\0';
            return i;
        }
        buffer[i++] = c;                ③
    }
    // We have received howMany characters.
    buffer[i] = '\0';                   ④
    return howMany;
}
Listing 9-9

USART sketch, receiveText() function

  •      ①     This loop will exit on one of two conditions:
    • The buffer is filled up with howMany characters, and a new line has not been seen.

    • The last character received was a new line.

  •      ②     If the character just received as a new line, overwrite it in the buffer with a string terminator and return to the caller.

  •      ③     Otherwise, store the character just read and loop around again.

  •      ④     If we exit the loop here, we have filled the buffer with howMany characters. Add a terminating character and return.

This is a pretty simple function to be honest, but it works. As long as the buffer has howMany characters plus one, it will work perfectly if the input received is less than the buffer size, which is something that is the responsibility of the programmer.

If the received data is longer, then it is possible that the USART will suffer a Data Overrun error and lose characters. You can see this by running the sketch and holding down a key until there has been more than the buffer size typed. Then press Enter.

The first buffer full of characters will be correctly displayed, and the first two characters after that will also be displayed. Anything after those two will be lost – unless your baud rate was low enough for the characters in the array to be displayed and the following two retrieved from the internals of the USART, before the third "extra" character started to be received.

Interrupts would be better, but even the Arduino’s interrupt-driven Serial interface can lose characters if there is a buffer overrun.

So far, that’s all we really need for simple USART receipt of data. What about sending data out? Listing 9-10 shows how we can send a single byte down the wire via the USART.
void sendByte(uint8_t c) {
    // Wait for bit UDRE0 to be set in UCSR0A then
    // buffer up the data byte.
    loop_until_bit_is_set(UCSR0A, UDRE0);      ①
    UDR0 = c;                                  ②
}
Listing 9-10

USART sketch, sendByte() function

  •      ①     Wait here until the data buffer is empty.

  •      ②     Add the character to be sent to the buffer where it will be wrapped in a frame and transmitted.

Again, sending one byte is no fun, so the sendText() function in Listing 9-11 sends a whole string of characters.
void sendText(const uint8_t *text) {
    // Transmit a string of text. One byte
    // at a time.
    uint8_t *i = text;
    while (*i)
        sendByte(*i++);
}
Listing 9-11

USART sketch, sendText() function

The preceding code simply walks the passed buffer sending each character out through the USART transmitter until it finds the end of the string character, which does not get sent.

The sendNumber() function in Listing 9-12 allows you to send numeric data in almost any radix (number base) that you desire, although the default is 10. This only handles signed integer values – I leave it as an exercise for the reader to write a sendFloat() function.
void sendNumber(const long x, const uint8_t r = 10) {
    // Transmit a long integer to the USART. Only 32 bits
    // can be sent.
    char buffer[40];           ①
    ltoa(x, buffer, r);        ②
    sendText(buffer);          ③
}
Listing 9-12

USART sketch, sendNumber() function

  •      ①     The length of a long is 32 bits, so 40 characters is enough of a buffer to cope without crashing. 232 is 4,294,967,296 and is 10 digits in size. There’s plenty room in 40 characters to hold a full unsigned number or a signed one, the smallest negative number being –2,147,483,648.

  •      ②     The ltoa (long to ASCII) function does all the hard work. It also adds a terminating character to the buffer – which is another reason for having a bit extra on the end.

  •      ③     The buffered ASCII representation of the number is then transmitted.

The communicate() function in Listing 9-13 demonstrates some of the code we have seen earlier in action. It sends out the number 232 – 1 in various formats.
void communicate() {
    const long number = 4294967295;
    sendText("Number = 2^32 -1 in HEX: ");           ①
    sendNumber(4294967295, 16);
    sendByte('\n');
    sendText("Number = 2^32 -1 in DEC: ");          ②
    sendNumber(number, 10);
    sendByte('\n');
    sendText("Number = 2^32 -1 in OCT: ");          ③
    sendNumber(number, 8);
    sendByte('\n');
    sendText("Number = 2^32 -1 in BIN: ");          ④
    sendNumber(number, 2);
    sendByte('\n');
    sendText("\n\n");
    sendText("Type some text or numbers ... \n");
}
Listing 9-13

USART sketch, communicate() function

  •      ①     This sends out a big number in hexadecimal. The number is 232 – 1 and is the biggest that will fit into a long data type.

  •      ②     This sends out a big number in decimal. In this case, it gets printed as –1 because the parameter is signed in the call to ltoa() and 232 – 1 is indeed –1 when dealing with signed values.

  •      ③     This sends out a big number in octal.

  •      ④     This sends out a big number in binary.

The well-known and much loved setup() function is shown in Listing 9-14.
void setup() {
    // Initialise the USART without needing Serial.
    setupUSART(9600);
    // Play with the USART.
    communicate();
}
Listing 9-14

USART sketch, setup() function

This function simply initializes the USART by calling the setupUSART() function and then calls the communicate() function to "show off"! Finally, Listing 9-15 is the main loop() function.
void loop() {
    uint8_t howManyChars;
    char buffer[101];
    // Buffer is one more than we want to receive.
    // Beware of buffer overruns, the code will lose
    // characters as the USART can only store two characters.
    howManyChars = receiveText(buffer, 100);
    sendNumber(howManyChars);
    sendByte('=');
    sendByte('>');
    sendText(buffer);
    sendByte('\n');
}
Listing 9-15

USART sketch, loop() function

The loop() function loops around – that’s its job after all – and receives strings of text from the Serial Monitor. That will need to be configured to add a new line to the end of the sent text, or the code will not print any output until it has received a full buffer of 100 characters of text.

The function just receives text and prints it out, preceded by the number of characters it received. It uses two calls here to sendByte() which could, obviously, have been a single call to sendText(); but that’s demonstrated in the next line.

If your input text is shorter than the buffer, the preceding discussion will work fine; if not, there’s a strong possibility that characters may be lost. If, as I did, you send 102 characters to a buffer that holds 100, you get two lines of output, the first 100 characters and then the two remaining. However, if you send more than that, you get exactly the same output – the first hundred get copied to the buffer, the next two are still stored internally in the USART, and the rest, sadly, get lost due to a buffer overrun.

Welcome to the world of serial communications!