9. ATmega328P Hardware: ADC and USART
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.
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.
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
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 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.
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 internal bandgap reference voltage
An external voltage on pin AIN0
Only one of these can be selected.
184.108.40.206 External Reference
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.
220.127.116.11 Internal Bandgap Reference
Write a 1binary to bit ACBG in register ACSR.
9.1.6 Select Sampled Voltage Source Pin
One of the pins A0–A7
18.104.22.168 Sample Voltage on Pin AIN1
Write a 1binary to bit AIN1D, in register DIDR1, to disable the I/O facilities on pin D7.
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.
22.214.171.124 Sample Voltage on Pins A0–A7
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
Analogue Comparator negative input summary
9.1.8 Comparator Outputs
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.
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.
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.
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 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
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.
126.96.36.199 Powering the ADC
188.8.131.52 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.
ADC prescaler settings and frequencies
Divide F_CPU by 1
Divide F_CPU by 2
Divide F_CPU by 4
Divide F_CPU by 8
Divide F_CPU by 16
Divide F_CPU by 32
Divide F_CPU by 64
Divide F_CPU by 128
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.
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.
184.108.40.206 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.
ADC reference voltage selection settings
Use the AREF pin as the reference voltage. The external voltage applied must not exceed VCC
Use the (internal) voltage on AVcc as the reference voltage. For best results, there should be a 100 nF capacitor between AREF and GND
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.
220.127.116.11 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 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.
ADC left/right alignment options
In code, you can read ADCW to get the correct result and not worry about reading ADCH and ADCL in the correct order.
18.104.22.168 Selecting an Input Source
ADC input voltage source settings
ADC0 (Arduino A0, AVR PC0)
ADC1 (Arduino A1, AVR PC1)
ADC2 (Arduino A2, AVR PC2)
ADC3 (Arduino A3, AVR PC3)
ADC4 (Arduino A4, AVR PC4)
ADC5 (Arduino A5, AVR PC5)
ADC6 (Arduino A6, AVR ADC6)
1.1 V internal bandgap
ADC7 (Arduino A7, AVR ADC7)
0 V (GND)
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.
22.214.171.124 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.
126.96.36.199 ADC Interrupt
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.
188.8.131.52 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.
ADC auto trigger sources
Free running mode
External Interrupt Request 0
Timer/counter 0 Compare Match A
Timer/counter 0 Overflow
Timer/counter 1 Compare Match B
Timer/counter 1 Overflow
Timer/counter 1 Input Capture Event
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.
184.108.40.206 Enabling the ADC and Initiating Conversions
The ADC starts consuming power.
The ADC prescaler starts counting.
If configured, auto triggering events will now initiate an ADC conversion.
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.
220.127.116.11 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 18.104.22.168, “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:
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
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.
-40oC = 010Dhex (269decimal)
25oC = 0160hex (352decimal)
125oC = 01E0hex (480decimal)
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.
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 developer help note is at https://microchipdeveloper.com/8avr:avradc.
The application note on calibrating the temperature sensor is at http://ww1.microchip.com/downloads/en/AppNotes/Atmel-8108-Calibration-of-the-AVRs-Internal-Temperature-Reference_ApplicationNote_AVR122.pdf, which may prove useful – if your Maths is better than mine!
The MySensors code mentioned in the preceding text can be found at https://github.com/mysensors/MySensors/blob/bde7dadca6c50d52cc21dadd5ee6d3623be5f3c6/hal/architecture/AVR/MyHwAVR.cpp.
In the United Kingdom, we say Maths, plural, from Mathematics. I believe in the United States it is Math, singular (from Mathematic?), which sounds really weird to my Scottish ears. At least we agree on Arithmetic.
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.
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.
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.
ADC example, the interrupt handler
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.
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.
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.
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
You will probably have figured out that you can just about define any baud rate you wish by setting UBRR0 to any given value.
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 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!
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.
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.
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.
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.
TX Data Register Empty
22.214.171.124 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.
126.96.36.199 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.
188.8.131.52 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.
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.
Any options that the code requires can now be safely ORd into the appropriate registers. The following examples will assume this mode of operation.
184.108.40.206 Powering the USART
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.
220.127.116.11 Choosing the USART Mode
Asynchronous USART (the default)
USART mode settings
Reserved – do not use
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.
18.104.22.168 Baud Rate Setting
22.214.171.124 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.
126.96.36.199 Setting Parity
USART parity settings
No parity (the default)
Reserved – do not use
188.8.131.52 Setting Stop Bits
USART stop bit settings
1 stop bit (the default)
2 stop bits
184.108.40.206 Setting Data Width
USART data width settings
5 bits data size
6 bits data size
7 bits data size
8 bits data size (the default)
Reserved – do not use
Reserved – do not use
Reserved – do not use
9 bits data size
220.127.116.11 Enabling Double-Speed Mode
18.104.22.168 Enabling Interrupts
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
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
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.
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.
22.214.171.124 Enabling Data Transmission
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.
126.96.36.199 Enabling Data Receipt
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.
188.8.131.52 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?
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.
184.108.40.206 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.
220.127.116.11 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.
18.104.22.168 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.
22.214.171.124 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.
126.96.36.199 USART Data Overrun
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.
188.8.131.52 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.
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.
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.
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.
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.
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.
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.
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.
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.
USART sketch, setup() function
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!