PIC Tutorial Eleven - Analogue Inputs
For
these tutorials you require Main Board 2 or 3, the Analogue Board, the LCD
Board and the RS232 Board. Download
zipped tutorial files.
The
16F876 and the 16F877 both include 10 bit analogue to digital converters,
the 876 provides a possible 5 inputs, and the 877 a possible 8 inputs, in
both cases there's only one actual converter - it's switched to each input
pin as required by setting a register.
There's
generally a lot of confusion about using the A2D inputs, but it's actually
really very simple - it's just a question of digging the information you
need out of the datasheets. The main confusion arises from actually setting
them up, in these tutorials I'll give proven working examples, which you can
easily modify to use as you wish.
There
are four main registers associated with using the analogue inputs, these are
listed in this table:
Main registers for analogue inputs.
Name |
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
ADRESH |
A2D Result Register - High Byte |
ADRESL |
A2D Result Register - Low Byte |
ADCON0 |
ADCS1 |
ADCS0 |
CHS2 |
CHS1 |
CHS0 |
GO/DONE |
- |
ADON |
ADCON1 |
ADFM |
- |
- |
- |
PCFG3 |
PCFG2 |
PCFG1 |
PCFG0 |
ADRESH
and ADRESL are fairly self explanatory, they are the registers that
return the result of the analogue to digital conversion, the only slightly
tricky thing about them is that they are in different memory banks.
ADCON0 Details
ADCON0
is split into four separate parts, the first part consists of the highest
two bits ADCS1 and ADCS0, and sets the clock frequency used
for the analogue to digital conversion, this is divided down from the system
clock (or can use an internal RC oscillator), as we are using a 20MHz
clock on the tutorial boards, we have to use Fosc/32 (as given in the table
below). So that's one setup decision already solved:
ADCS1 |
ADCS0 |
A/D Conversion Clock
Select bits. |
Max. Clock Freq. |
0 |
0 |
Fosc/2 |
1.25MHz |
0 |
1 |
Fosc/8 |
5MHz |
1 |
0 |
FOsc/32 |
20MHz |
1 |
1 |
Frc (Internal A2D RC Osc.) |
Typ. 4uS |
The
second part of ADCON0 consists of the next three bits, CHS2,CHS1
and CHS0, these are the channel select bits, and set which input pin
is routed to the analogue to digital converter. Be aware that only the first
five (AN0-AN4) are available on the 16F876, the next three (AN5-AN7) are
only available on the 16F877. Also notice that AN4 uses digital pin RA5, and
not RA4 as you would expect. As the tutorial analogue input board only has
two inputs, connected to AN0 and AN1, this reduces the decision to two
possibilities, either 000 for AN0, or 001 for AN1, and we simply alter them
as we switch between the two inputs.
CHS2 |
CHS1 |
CHS0 |
Channel |
Pin |
0 |
0 |
0 |
Channel0 |
RA0/AN0 |
0 |
0 |
1 |
Channel1 |
RA1/AN1 |
0 |
1 |
0 |
Channel2 |
RA2/AN2 |
0 |
1 |
1 |
Channel3 |
RA3/AN3 |
1 |
0 |
0 |
Channel4 |
RA5/AN4 |
1 |
0 |
1 |
Channel5 |
RE0/AN5 |
1 |
1 |
0 |
Channel6 |
RE1/AN6 |
1 |
1 |
1 |
Channel7 |
RE2/AN7 |
The
third part is a single bit (bit 2), GO/DONE, this bit has two
functions, firstly by setting the bit it initiates the start of analogue to
digital conversion, secondly the bit is cleared when the conversion is
complete - so we can check this bit to wait for the conversion to finish.
The
fourth part is another single bit (bit 0), ADON, this simply turns
the A2D On or Off, with the bit set it's On, with the bit cleared it's Off -
thus saving the power it consumes.
So for
our application the data required in ADCON0 is binary '10000001' to read
from AN0, and binary '10001001' to read from AN1. Bit 1 isn't used, and can
be either '0' or '1' - in this case I've chosen to make it '0'. To initiate
a conversion, we need to make Bit 2 high, we'll do this with a "BCF ADCON0,
GO_DONE" line. Likewise, we'll use a "BTFSC ADCON0, GO_DONE" line to check
for the end of conversion.
Bits required in ADCON0.
Input |
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
AN0 |
1 |
0 |
0 |
0 |
0 |
0 |
- |
1 |
AN1 |
1 |
0 |
0 |
0 |
1 |
0 |
- |
1 |
ADCON1 Details
ADCON1
is really a little more complicated, although it's only split into two
sections. The first section is a single bit, ADFM, this is the Result
Format Selection Bit, and selects if the output is Right Justified (bit
set) or Left Justified (bit cleared). The advantage of this is that it makes
it very easy to use as an 8 bit converter (instead of ten bit) - by clearing
this bit, and reading just ADRESH, we get an 8 bit result, ignoring the two
least significant bits in ADRESL. For the purposes of these tutorials, I'm
intending using the full ten bits - so this bit will be set.
PCFG3-0
are probably the most complicated part of setting the A2D section, they set
a lot of different options, and also limit which pins can be analogue, and
which can be digital:
PCFG3:
PCFG0 |
AN7
RE2 |
AN6
RE1 |
AN5
RE0 |
AN4
RA5 |
AN3
RA3 |
AN2
RA2 |
AN1
RA1 |
AN0
RA0 |
Vref+ |
Vref- |
0000 |
A |
A |
A |
A |
A |
A |
A |
A |
Vdd |
Vss |
0001 |
A |
A |
A |
A |
Vref+ |
A |
A |
A |
RA3 |
Vss |
0010 |
D |
D |
D |
A |
A |
A |
A |
A |
Vdd |
Vss |
0011 |
D |
D |
D |
A |
Vref+ |
A |
A |
A |
RA3 |
Vss |
0100 |
D |
D |
D |
D |
A |
D |
A |
A |
Vdd |
Vss |
0101 |
D |
D |
D |
D |
Vref+ |
D |
A |
A |
RA3 |
Vss |
0110 |
D |
D |
D |
D |
D |
D |
D |
D |
Vdd |
Vss |
0111 |
D |
D |
D |
D |
D |
D |
D |
D |
Vdd |
Vss |
1000 |
A |
A |
A |
A |
Vref+ |
Vref- |
A |
A |
RA3 |
RA2 |
1001 |
D |
D |
A |
A |
A |
A |
A |
A |
Vdd |
Vss |
1010 |
D |
D |
A |
A |
Vref+ |
A |
A |
A |
RA3 |
Vss |
1011 |
D |
D |
A |
A |
Vref+ |
Vref- |
A |
A |
RA3 |
RA2 |
1100 |
D |
D |
D |
A |
Vref+ |
Vref- |
A |
A |
RA3 |
RA2 |
1101 |
D |
D |
D |
D |
Vref+ |
Vref- |
A |
A |
RA3 |
RA2 |
1110 |
D |
D |
D |
D |
D |
D |
D |
A |
Vdd |
Vss |
1111 |
D |
D |
D |
D |
Vref+ |
Vref- |
D |
A |
RA3 |
RA2 |
As I
mentioned above, this part looks rather complicated - but if we split it
down, it starts to make more sense. There are actually four different
options being set here:
- Setting a pin to be an analogue input.
- Setting a pin to be a digital input.
- Setting the positive reference for the converter (Vref+).
- Setting the negative reference for the converter (Vref-).
For a
start we need to decide what settings we actually require - first we are
only using analogue inputs AN0 and AN1, which if you look down the columns
eliminates four of the possibilities (0110, 0111, 1110 and 1111, shaded in
blue). Secondly we are using a VRef- of Vss (Ground), so that eliminates
another four (1000, 1011, 1100 and 1101, shaded in yellow) - so now we've
only got eight possibly choices left (down to 50% already). Thirdly we are
using an external VRef+ reference, so we need RA3 allocating to Vref+, this
eliminates a further four (0000, 0010, 0100 and 1001, shaded in green). This
brings us down to four possible choices and, to be honest, any of the four
would work quite happily - however, one of our requirements was 'two
analogue inputs', this eliminates a further three possibilities (0001, 0011
and 1010, shaded in red) - which leaves the only option which fits all our
requirements '0101', so this is the value we will need to write to
PCFG3:PCFG0.
So now
we've decided what we need to set ADCON1 to, binary '10000101', with 0's in
the places of the unused bits, this gives us two analogue inputs, Vref+ set
to RA3, and Vref- set to Vss.
Bits required in ADCON1.
Name |
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3 |
Bit 2 |
Bit 1 |
Bit 0 |
ADCON1 |
1 |
- |
- |
- |
0 |
1 |
0 |
1 |
Now we
know the setting up details, it's time for a little more explaining about
exactly what it all means, I've mentioned earlier that the A2D has '10 bit'
resolution, this means the output of the A2D can vary from 0 (all bits '0')
to 1023 (all bits '1'). A number from 0-1023 probably isn't too helpful,
because you don't know what they represent. This is where Vref+ and Vref-
come in!. When the output of the A2D = 1023, the analogue input is at Vref+,
when the A2D output = 0, then the analogue input is at Vref-. In our case,
with the TL341 reference on RA3, 1023 is about 2.5V, and with Vref- at Vss,
0 from the A2D equals zero volts on the input.
So, to
recap, 1023 represents 2.5V, and 0 represents 0V. Numbers in between
represent voltages in between, to work out what they actually represent, we
need a little mathematics. Dividing 2.5V by 1023 gives 0.00244, this is the
voltage resolution of out A2D, so a reading of 1 represents 0.00244V (or
2.44mV), and a reading of 2 represents 0.00488V (or 4.88mV), and so on. As
you'll see later on (in the programming tutorials) it's important to know
the voltage resolution of the A2D, in order to calculate the voltage. Now
while I said the voltage resolution is 0.00244, I was rounding the figure
off, the actual value (from Windows Calculator) is
0.00244379276637341153470185728250244 - multiplying 1023 by 0.00244 only
gives 2.49612V, and not the 2.5V it should be. Although this is only a
0.388% error, it doesn't look good - I'd really like the display to read
from 0 to the maximum input, with out silly limitations like that.
I
considered this while designing the analogue input board, and made a
decision to get over the problem in the hardware. The amplifiers on the
inputs have attenuators feeding them, this gives the overall amplifier an
adjustable gain of around minus 4, so 10V (or so) input will give 1000
output from the A2D. So by adjusting the gain of the input amplifiers, we
can use an input voltage range from 0-10.23V, which (by no coincidence at
all) allows us to get a 0-1023 reading from the A2D converter giving a
resolution of 10mV, which is a theoretical accuracy of around 0.1%
(obviously the system itself isn't this accurate, nor is the meter we'll use
to calibrate it). Another advantage of this is that it calibrates the
voltage reference chip as well, this probably isn't going to be exactly
2.5V, by calibrating the inputs against a known meter we take care of this
as well.
PIC Assembler routines.
As
usual, this tutorial will present simple reusable routines for using the
analogue inputs, as I mentioned at the beginning, it's actually really very
easy to use, once you've worked through the datasheets and found out how you
need to set things. For the actual programming, this is reduced down to two
small routines:
Init_ADC
; Set ADCON0
movlw b'10000001'
movwf ADCON0
; Set ADCON1
BANKSEL ADCON1
movlw b'10000101'
movwf ADCON1
BANKSEL ADCON0
return
This
routine simply sets the values of ADCON0 and ADCON1 with the values we
decided on in the discussion earlier, the only point to note is the BANKSEL
commands before and after ADCON1. This register is in a different register
bank to ADCON0, so we need to select the correct bank before accessing the
register, as a matter of course we return to the original register bank
afterwards.
Read_ADC
bsf ADCON0, GO_DONE ;initiate conversion
btfsc ADCON0, GO_DONE
goto $-1 ;wait for ADC to finish
movf ADRESH,W
andlw 0x03
movwf NumH
BANKSEL ADRESL
movf ADRESL,W
BANKSEL ADRESH
movwf NumL ;return result in NumL and NumH
return
This
second routine is the one which actually reads the analogue input, the first
line initiates the conversion, and the second and third wait for it to
complete. The second section simply reads the converted value from the two
registers, and stores them in variables for later use. Notice the bank
switching again around ADRESL, this is because ADRESL is in a different
register bank, it's important to notice that we move it's value to W, then
switch banks back BEFORE storing it in the variable NumL - if we
don't switch back it gets stored in the wrong register. Notice I'm AND'ing
ADRESH with 0x03, this is because we only use the lower two bits of ADRESH,
and although the upper six bits should all be zero - I like to make sure.
So all
we need to do to use the A2D is to "call Init_ADC" during the programmes
initialisation, and then "call Read_ADC" whenever we wish to read a value.
In order to select the individual two analogue channels, I've simply
expanded "Init_ADC" to two routines, "Init_ADC0" and "Init_ADC1", these
simply set the analogue converter to the correct input pin.
These tutorials use the Analogue Board on PortA, the
LCD Board on PortB, and the RS232 Board on PortC.
Tutorial 11.1 - requires main Board Two or Three, Analogue
Board, LCD Board.
This
tutorial reads a single channel of the A2D converter and displays it on the
LCD. First the program sets up the registers explained above, then the rest
of the program runs in an endless loop. It reads the analogue input, which
gives a value from 0-$3FF (in hexadecimal), then calls a routine which
converts that to decimal, giving 0-1023 (representing 0-10.23V). Lastly this
decimal value is displayed on the LCD, followed by a space, and the
same value in hexadecimal. The program then wait for 100mS and jumps back to
the start of the loop and runs again, this gives roughly 10 readings a
second.
Tutorial 11.2 - requires as above.
Pretty
much the same as Tutorial 11.1, except this time we read both channels and
display the two voltages, this demonstrates how to switch between the
different channels using the two different "Init_ADC" routines. In order to
keep 10 readings per second, the delay has been reduced to 50mS, so we get
20 readings per second, but still only 10 on each channel.
Tutorial 11.3 - requires as above.
Similar
to the previous Tutorials, but this time we add a decimal point and a 'V'
for voltage at the end of the decimal display, and we don't bother showing
the hexadecimal value - just as you would for a simple voltmeter.
Tutorial 11.4 - requires RS232 board as well.
This
time we add an extra board, the RS232 Board. The program is essentially
Tutorial 11.3, but this time we add a serial output routine to send the data
via RS232 to a PC. For the RS232 output we don't send the 'V' at the end,
but we do send CR/LF in order to display each reading on a separate line.
I've implemented this by modifying the LCD_Char routine and adding a couple
of Flags - LCD and RS232 - by setting these flags ON or OFF we can print to
either the LCD, the RS232 output, both, or neither.
|