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. 
    
  |