Assignment title: Information
72
9 Analogue to Digital Conversion Module
9.1 Aim
This laboratory practical will investigate methods for configuring and using the analogue‐to‐digital
convertor (ADC) module on the PIC16F877A to convert voltages from a potentiometer and light
dependent resistor (LDR) located on the E‐block sensor board attached to PORTA. The relationship
between converted digital values to physical quantities such as voltage will be investigated.
9.2 Learning Outcomes
After this laboratory practical you will:
x Understand how a physical quantity can be represented by a digital value through the use
of an analogue transducer and the process of analogue to digital conversion.
x Understand the capabilities and limitations of the 10 bit ADC module on the PIC16F877A.
x Be able to configure the PIC16F877A ADC module correctly.
x Be able to create useful code to monitor analogue voltages using polling and interrupts.
x Implement a library file of useful ADC functions.
9.3 Background
9.3.1 Introduction
An analogue‐to‐digital convertor (ADC) is a system that converts an analogue signal (voltage) that is
continuous in both time and magnitude, into a digital representation. In order to do this, the signal is
first sampled before being quantised. Sampling is the act of taking a ‘snapshot’ of the voltage at a
point in time. Quantisation is the process of converting a sampled voltage into a discrete digital value.
An ADC converts an analogue signal into a digital signal that is discrete in both time and amplitude.
In most applications ADC conversion takes place at a regular rate defined by the sampling rate. The
Nyquist‐Shannon sampling theorem stipulates that the sampling rate must be at least double the
highest frequency contained in a band‐limited continuous‐time signal. A failure to adhere condition
can lead to aliasing, where high frequency content appears incorrectly as lower frequency content in
the digital signal. Aliasing can be avoided by band‐limiting the analogue signal using an anti‐aliasing
filter.
There are many different types of ADCs e.g. flash, sigma‐delta, successive approximation, pipelined.
Each design has its own strengths and weaknesses in terms of key parameters such as speed,
complexity, resolution, linearity etc. The type of ADC design chosen in any give applications will
depend on the requirements of that application.
Commercial ADCs generally come as dedicated ICs and may require other external components such
as voltage references to operate. ADCs are also often found integrated into MCUs to allow the
conversion of analogue signals from transducers in embedded systems applications. A transducer is a
device that converts one form of physical quantity to another. Some examples of common transducers73
are microphones (sound), temperature sensors, light‐level sensors, potentiometers (linear/rotary
position).
9.3.2 PIC16F877A ADC module
The PIC16F877A has a single on board ADC module which has a resolution of 10 bits. This means that
it can convert an analogue voltage into one of 2 1024 10 separate digital values ranging from 0‐1023.
In front of the ADC is an 8‐channel multiplexer (MUX) which allows the selection of any one of 8
channels connected to external pins on PORT A (RA0 to RA5) and PORT E (RE0 to RE2). The MUX allows
up to 8 separate analogue inputs to be converted using a single ADC unit, but only one input can be
converted at any one time. This means that the maximum possible sampling rate is reduced in
proportion to the number of multiplexed channels converted e.g. sampling 4 channels will reduce the
maximum sampling rate by a factor of 4.
Figure 9‐1 Schematic of the PIC16F877A ADC module
The ADC can supply its own voltage reference internally, or if required external voltage references can
be supplied. The reference voltages (VREF+ and VREF‐) are important because they define the voltage
conversion range of the ADC. Generally speaking the voltage resolution of an ADC is defined by,
ADC Resolution
2
REF REF
N
V V
We will be using the on board ADC module in conjunction with internally generated voltage
references, which are sourced from the power supply rails. In the case of the Matrix Multimedia E‐
block development system the internal voltage references are thus,
5 V
0 V
REF DD
REF SS
V V
V V
So the ADC voltage resolution available is,
10
5 0
ADC Resolution 4.88 mV
2 2
REF REF
N
V V
74
In general, it is important to match the conversion range of an ADC with the anticipated operating
range of the analogue signal you are monitoring. If the range is too small you will experience clipping
of the converted digital signal as the analogue voltage goes out of range. If the range is too high you
will reducing the effective resolution at which the ADC is operating.
Note that other parameters can affect the effective resolution of a real‐world ADC including linearity,
missing codes and non‐monotonicity. However, such considerations are outside of the scope of this
laboratory practical.
9.3.3 ADC conversion clock requirements
The PIC16F877A ADC module works on the principle of successive‐approximation. This means that
each ADC conversion requires a number of conversion cycles to arrive at the final converted digital
value. In total 12 conversion cycles are required (one for each bit of resolution plus two for overhead).
The minimum conversion cycle time (TAD) is 1.6 µs if the ADC is to achieve its specified accuracy. The
conversion cycle time is determined by the ADC conversion clock.
The ADC conversion clock can be supplied internally from the crystal oscillator driving the MCU via a
prescaler or via a dedicated RC oscillator (for low power applications). There are seven software
selectable options in total:
x 2 TOSC
x 4 TOSC
x 8 TOSC
x 16 TOSC
x 32 TOSC
x 64 TOSC
x Internal RC oscillator
Since we are using a 3.2786 MHz crystal to drive the PIC16F877A in the laboratory, the crystal clock
interval is, T f OSC OSC 1/ 305.2 ns . In order to maintain the criteria for a 1.6 µs conversion clock
interval then we need to select the conversion clock source as 8 TOSC which yields a conversion clock
interval, TAD 2.441 μs . The total conversion time per sample is then 12 29.3 TAD μs .
9.3.4 ADC acquisition time requirements
The analogue voltage input to the ADC module is sampled before conversion by a sample‐and‐hold
circuit, which comprises a capacitor and switch. It is necessary to allow enough time for the capacitor
to charge and for the amplifier that buffers the signal to the ADC to settle if the sampled analogue
voltage is to be accurate enough to provide true 10 bit resolution on conversion. This so called
acquisition time (TACQ) also depends on ambient temperature and the source impedance of the
analogue input being sampled.
Detailed information on the calculation of acquisition time can be found in the PIC16F977A datasheet.
A worst case value of TACQ = 19.72 µs can be calculated which assumes the maximum recommended
source impedance of 2.5 kΩ and an ambient temperature of 50°C. In summary, ensuring a minimum
period of 20 µs per conversion for signal acquisition will ensure operation of the ADC to the specified
accuracy. It may however be possible to reduce this acquisition time further if one has detailed
knowledge of the impedance of the analogue source.75
9.3.5 Configuring the PIC16F877A ADC module
The ADC module is configured through two SFRs, ADCON1 and ADCON2. These registers contain
bitfields to do the following,
x Set the ADC conversion clock source.
x Select which MUX channel to convert.
x Start an ADC conversion.
x Turn the ADC module on/off.
x Set the justification of the conversion result stored in the in the ADCRESH and ADCRESL
registers.
x Assign which of the 8 analogue available input pins are assigned to the ADC module (some can
be configured as GPIO instead).
x Select internal/external voltage reference sources.
The contents of the ADCON0 and ADCON1 SFRs are summarised in figure 9‐2 and figure 9‐3. This looks
quite complicated at first, but luckily the ADC should be configured in a fixed manner for correct
operation in these laboratory practicals. Only the MUX channel selection bitfield (CHS2:0) needs to be
changed for your application.
Figure 9‐2 ADCON0 SFR76
Figure 9‐3 ADCON1 SFR
Table 9‐1 summarises the configuration requirements for the ADC.
Table 9‐1 Summary of ADC configuration requirements
Bit field Value Reason
ADCS2:0 4 Need to set the conversion clock interval to 8 TOSC when the MCU is clocked
at 3.2768 MHz (see section 9.3.3). This ensures TAD>1.6 µs.
CHS2:0 0 Set AN0 as the default MUX channel for conversion. This is connected to the
LDR on the E‐block sensor board.
ADON 1 The ADC module should be turned on ready for use
ADFM 1 Set results justification to right‐justified. This allows easy combination of
ADRESH and ADRESL to yield a 10 bit result (see section 9.3.7)
PCFG3:0 0 For the purposes of this lab, the analogue input pins are not used as GPIO
so they can all be assigned as analogue inputs. The voltage refs, VREF+/VREF‐
need to be sourced from the power supply rails, VDD (5 V) and VSS (0 V)
respectively.
The required configuration for ADCON0 and ADCON1 is summarised in the tables below (X represents
a ‘don’t care’ state). Note: The ADCS bitfield is split over both registers. The MSB (ADCS2) is contained
in bit 6 of ADCON1, while the LSBs are located in bits 7 & 8 of ADCON0.77
Table 9‐2 ADCON0 Configuration
ADCS1 ADCS0 CHS2 CHS1 CHS0 GO/DONE ‐ ADON
0 1 0 0 0 0 X 1
Table 9‐3 ADCON1 Configuration
ADFM ADCS2 ‐ ‐ PCFG3 PCFG2 PCFG1 PCFG0
1 1 X X 0 0 0 0
This configuration can be achieved by writing the following values to the ADC configuration SFRs
(Don’t care’ states are written as zeroes in this instance).
ADCON0 = 0x41;
ADCON1 = 0xC0;
9.3.6 Starting an ADC conversion
Once the ADC module has been configured correctly and turned on, starting an ADC conversion
requires the following two actions,
x Wait the required minimum acquisition time (20 µs) has elapsed to allow the analogue voltage
to stabilise in the sample and hold circuit.
x Set the Go/Done bit in the ADCON0 SFR to command the conversion process to start.
These actions can easily be implemented using the code below
__delay_us(20); // Wait for acquisition time (worst case 20 us)
ADCON0bits.GO = 1; // Set GO/DONE Bit to start conversion
When the conversion is complete then the Go/Done bit is automatically reset by the MCU. This
condition can be checked by polling. The following code sets up a do‐nothing while loop that polls the
GO/DONE bit, and exits only when it has been reset.
while(ADCON0bits.GO==1); // Wait for GO bit to clear (conversion complete)
When the result is ready it is stored in the ADC results SFRs, ADRESH and ADRESL.
9.3.7 Retrieval and storage of ADC results
It is necessary to use two SFRs to store the 10 bit result of a conversion by the ADC module. This is
because of the maximum 8 bit register width set by the PIC16 family architecture. The ADC result
registers are called ADRESH and ADRESL. Combining both of these 8 bit SFRs provides a 16 bit wide
result register. The ADC result itself is only 10 bits wide so there is an option provided to set the
justification of the result in the register.
Results can be stored in either a left‐justified or right justified manner depending on the state of the
ADFM bit in the ADCON1 SFR. Regardless of the selected justification type, the unused bits are padded
with zeroes.78
Figure 9‐4 Justification of the ADC result
The simplest way of retrieving the result of the 10 bit ADC conversion is to configure right‐justification
(ADFM=1) and use a bitwise shift to combine the registers before assigning the result to a variable of
type unsigned int,
result = (ADRESH<<8) + ADRESL; // Combine to produce final 10 bit result
Alternatively, if the full resolution of the ADC is not important then it is possible use left‐justification
(ADFM=0) and simple retrieve the high byte as follows,
result = ADRESH; // Retreive 8 bit resolution result directly
This will provide an effective ADC resolution of 8 bits because the 2 LSBs of the result, stored in the
ADRESL register, are ignored.
9.3.8 ADC conversion complete interrupt
There is a peripheral interrupt available which is triggered whenever an ADC conversion is completed.
You can interact with this by using the ADIE/ADIF bits in the PIE1/PIR1 SFRs. Don’t forget to enable
peripheral interrupts by setting the PEIE bit in the INTCON SFR if you wish to use this facility. Using
interrupts allows the MCU to get on with other useful tasks while an ADC conversion takes place. See
the PIC16F877A datasheet for more information on using the ACD conversion complete interrupt.
9.4 Procedure
9.4.1 Exercise 1 – Configuring and using the ADC module
In this exercise we will create some basic ADC functions and investigate their operation, utilising the
LCD to provide output.
Create a new project in the MPLAB X IDE, then create an empty source file and populate it
with the code listing below. Note: You will need to place a copy of the LCD library files in the
project directory in order to build the project successfully.
// Filename: ADClib.c
// Version: 1.0
// Date:
// Author:
// Purpose : Provides functions to easily use ACD to monitor Matrix
// : Multimedia sensor boards
#include 79
#include "LCDdrive.h"
#define _XTAL_FREQ 3276800 // Required for the __delay_us and __delay_ms macros
//Function Prototypes
void ADC_initialise(void);
unsigned int ADC_read(void);
void ADC_channel_select(unsigned char channel);
void main(void)
{
TRISB=0x00;
LCD_initialise();
ADC_initialise();
while(1)
{
LCD_cursor(0,0);
LCD_putsc("AN0 (LDR) ");
ADC_channel_select(0);
LCD_cursor(10,0);
LCD_display_value(ADC_read());
LCD_cursor(0,1);
ADC_channel_select(1);
LCD_putsc("AN1 (POT) ");
LCD_cursor(10,1);
LCD_display_value(ADC_read());
__delay_ms(250);
}
}
/* ADC_Config Configures the ADCON1 register for Fosc/8 and enables the ADC
* function once before prior to using any of the LCD display functions
* contained in this library. */
void ADC_initialise(void)
{
TRISA = 0x03; // Assign AN0 (RA0) and AN1 (RA1) as input
ADCON1bits.ADCS2 = 0; // select ADC conversion clock select as Fosc/8
ADCON0bits.ADCS=0x01; // this provides the required minimum conversion time
// with a 3.2768 MHz clock
ADCON1bits.ADFM=0x01; // Set results to right justified
ADCON1bits.PCFG=0x00; // Assign all ADC inputs as analogue
ADCON0bits.ADON = 1; // Turn ADC On
}
/* ADC_Read reads the current analogue reading channel selected. It starts the
* conversion by setting the Go/Done bit. Conversion is complete when the bit
* is cleared by the MCU so a polling loop is set up detect this. After
* conversion the ADRESH and ADCRESL are combined to provide a 10 bit result.
*/
unsigned int ADC_read(void)
{
unsigned int result;
__delay_us(20); // Wait for acquisition time (worst case 20 us)
ADCON0bits.GO = 1; // Set GO Bit to start conversion80
while(ADCON0bits.GO==1); // Wait for GO bit to clear (conversion complete)
result =(ADRESH<<8)+ADRESL; // Combine to produce final 10 bit result
return(result);
}
/* ADC_channel_select() selects the current channel for conversion.
* On the E‐block sensor board:
* Channel 0 (AN0) is connected to the LDR.
* Channel 1 (AN1) is connected to the potentiometer.
*/
void ADC_channel_select(unsigned char channel)
{
ADCON0bits.CHS=channel; // This selects which analogue input
// to use for the ADC conversion
}
Build the project and upload the generated HEX file onto the PIC16F877A. Observe the display
on the LCD and monitor what happens when you:
a) Turn the potentiometer on the E‐block sensor board.
b) Place you hand over the top of the LDR on the E‐block sensor board.
Study the code carefully and write a short summary of the purpose of each of the functions.
Pay attention the parameter and return data types and justify provide a justification for the
use of each one.
Write down a flow chart describing the operation of the main() function using the subroutine
call box (see figure 8‐5).
Write another flowchart describing the operation of the ACD_read() function.
MPLAB provides a useful feature, Call Graph for mapping the interconnectivity of functions
in C code. This can be useful for generating hierarchy diagrams for software design reports.
You can access the call graph by right clicking on a function in the editor and selecting Show
Call Graph from the context menu.
Figure 9‐5 Call Graph in MPLAB X
Right click on the following line and select Show Call Graph, to produce a call graph of the
main() function
void main (void)81
You can right click on the boxes representing functions and select Expand Callees from the
resulting context menu to see further levels of function calls. Boxes can also be dragged
around to create a neat result.
Arrange the boxes in the Call Graph output window until you have a hierarchical structure
similar to that shown in figure 9‐5. Right click on whitespace in the window and select
Export… to bring up a dialog window which allows you to save the result as a .png file. Insert
this into your logbook.
9.4.2 Exercise 2 ‐ Building an ADC function library
In this exercise we will build a library of useful ADC functions so that we can easily make use of the
ADC module on board the PIC16F877A. Once created, this library can be used in later
projects/assignments.
Create a new project called ADClib in the MPLAB X IDE, create two empty source files called
ADClib.c and main.c.
Create a third empty file and name it ACDlib.h
Restructure the code from the previous exercise so that the definitions for the three ADC
functions ADC_initialise(), ADC_read() and ADC_channel_select() are placed in ADClib.c.
Place the prototypes for the ADC functions in ADClib.h
Place the main() function from the previous exercise into the main.c file. Be sure to put the
required #include directive at the top so you can access the ADC functions that have now
been moved out to the file ADClib.c.
#include “ADClib.h”
Build the project and ensure it is operating correctly. Remember that you will need to place
a copy of the LCD library files in the project directory in order for it to build successfully.
You have now created a library of useful ADC functions. To utilise them in other projects just
place a copy of the ADClib.c and ADClib.h files in the project directory. Don’t forget that you
must always run the ACD_initialise() function once prior to using the ADC in order to make
sure the module is properly configured.
9.4.3 Exercise 3 – Converting ADC values to represent physical quantities
In this exercise we will utilise our newly created ADC functions library to investigate the conversion of
ADC values to represent actual physical quantities such as voltage.
Create a new project in the MPLAB X IDE, then create an empty source file and populate it
with the code listing below. Note: You will need to place a copy of the LCD and ADC library
files in the project directory in order to build the project successfully.
Create an empty source file and insert the following template code:
// Filename: Lab9Ex3
// Version: 1.082
// Date:
// Author:
// Purpose : Provides functions to easily use ACD to monitor Matrix
// : Multimedia sensor boards
#include
#include "LCDdrive.h"
#include "ADClib.h"
#define _XTAL_FREQ 3276800 // Required for the __delay_us and __delay_ms macros
void main(void)
{
TRISB=0x00;
LCD_initialise();
ADC_initialise();
// Insert your code solution here
}
Expand the provided template code so that the current potentiometer value in volts is
displayed to three decimal places on the top line of the LCD display. The value should be
appended with the correct units (V). The display should be updated every 200 ms using the
timer 1 overflow interrupt. The background code executing in the main() function should deal
with making the required ADC conversions. Ensure you coded solution is modular i.e.
separate the tasks logically into individual functions.
You will need to use the LCD_display_float() function provided in the LCD library. Review the
LCD header file, LCDdrive.h for guidance in using this function.
The calculation of the ADC resolution for one LSB is described in section 9.3.2. Simply multiply
the ADC result by the resolution to determine the analogue voltage.
You may view the correct operation for your developed code in this task by uploading the
following HEX file:
J:\Embedded Systems\MPLAB\Examples\ADCVoltage.hex
Write down a high level flow chart of your main() function describing your code which shows
function calls made.
Write a high level flow chart representation of your ISR.
Consider the combination of acquisition time and conversion time required to make an
accurate ADC conversion. What limit does this does this place on the maximum sampling rate
possible, assuming only a single ADC channel is converted? Refer to sections 9.3.3 and 9.3.4
and show any working.
9.4.4 Exercise 4 – Using the ADC conversion complete interrupt
Create a new project in the MPLAB X IDE, then create an empty source file. Add copies of the
the LCD and ADC library files to the project directory.
Restructure the code from the previous exercise so that:83
x The LCD continues to be updated using the Timer1 overflow interrupt at 200 ms intervals.
x The ADC conversion is driven using the ADC conversion complete interrupt (ADIE/ADIF).
Every time the interrupt occurs, the ISR should store the conversion result and start a new
conversion.
Note: In your solution, the main() function should contain only the initial configuration code
and a while(1) loop.