Skip to main content
Untitled Document

ADC - Analog-to-Digital Conversion

ADC - Analog-to-Digital Conversion

ADC - Analog-to-Digital Conversion

Analog-to-Digital Conversion is yet another nice feature you can get with a PIC. It's basically used to convert a voltage as an analog source (continuous) into a digital number (discrete).

ADC with water...

To better understand ADC, imagine you have some water going out of a pipe, and you'd like to know how many water it goes outside. One approach would be to collect all the water in a bucket, and then measure what you've collected. But what if water flow never ends ? And, more important, what if water flow isn't constant and you want to measure the flow in real-time ?

The answer is ADC. With ADC, you're going to extract samples of water. For instance, you're going to put a little glass for 1 second under the pipe, every ten seconds. Doing the math, you'll be able to know the mean rate of flow.

The faster you'll collect water, the more accurate the rate will be. That is, if you're able to collect 10 glasses of water each second, you'll have a better overview of the rate of water than if you collect 1 glass each ten seconds. This is the process of making a continuous flow a discrete, finite value. And this is about resolution, one important property of ADC (and this is also about clock speed...). The higher the resolution, the more accurate the results.

Now, what if the water flow is so high that your glass gets filled before the end of the sample time ? You could use a bigger glass, but let's assume you can't (scenario need...). This means you can't measure any water flow, this one has to be scaled according to your glass. On the contrary, the water flow may be so low samples you extract may not be relevant related to the glass size (only few drops). Fortunately, you can use a smaller glass (yes, scenario need) to scale down your sample. That is about voltage reference, another important property.

Leaving our glass of water, many PICs provide several ADC channels: pins that can do this process, measuring voltage as input. In order to use this peripheral, you'll first have to configure how many ADC channels you want. Then you'll need to specify the resolution, usually using 8 bits (0 to 255), 10 bits (0 to 1024) or even 12 bits (0 to 4096). Finally, you'll have to setup voltage references depending on the voltage spread you plan to measure.

ADC with jallib...

As usual, Microchip PICs offers a wide choice configuring ADC:
  • Not all PICs have ADC module (...)
  • Analog pins are dispatched differently amongst PICs, still for user's sake, they have to be automatically configured as input. We thus need to know, for each PIC, where analog pins are...
  • Some PICs have their analog pins dependent from each other, and some are independent (more on this later)
  • Clock configuration can be different
  • As previously stated, some PICs have 8-bits low resolution ADC module, some have 10-bits high resolution ADC module1
  • Some PICs can have two external voltage references (VRef+ and VRef-), only one voltage reference (Vref+ or Vref-) and some can't handle external voltage references at all
  • (and probably other differences I can't remember :)...

Luckily most of these differences are transparent to users...

Dependent and independent analog pins

OK, let's write some code ! But before this, you have to understand one very important point: some PICs have their analog pins dependent from each other, some PICs have their analog pins independent from each other. "What is this suppose to mean ?" I can hear...

Let's consider two famous PICs: 16F877 and 16F88. 16F877 datasheet explains how to configure the number of analog pins, and vref, setting PCFG bits:

Figure 1. 16F877 ADC channels are controlled by PCFG bits
Want 6 analog pins, no Vref ? Then PCFG bits must be set to 0b1001. What will then be the analog pins ? RA0, RA1, RA2, RA3, RA5 and RE0. "What if I want 7 analog pins, no Vref ?" You can't because you'll get a Vref pin, no choice. "What if I want 2 analog pins being RE1 and RE2 ?" You can't, because there's no such combination. So, for this PIC, analog pins are dependent from each other, driven by a combination. In this case, you'll have to specify:
  • the number of ADC channels you want,
  • and amongst them, the number of Vref channels

Now, let's consider 16F88. In this case, there's no such table:

Figure 2. 16F88 ADC channels are controlled by ANS bits

Mmmh... OK, there are ANS bits, one for each analog pins. Setting an ANS bit to 1 sets the corresponding pin to analog. This means I can set whatever pin I want to be analog. "I can have 3 analog pins, configured on RA0, RA4 and RB6. Freedom !"

Analog pins are independent from each other in this case, you can do what you want. As a consequence, since it's not driven by a combination, you won't be able to specify the number of ADC channels here. Instead, you'll use set_analog_pin() procedure, and if needed, the reverse set_digital_pin() procedure. These procedures takes a analog pin number as argument. Say analog pin AN5 is on pin RB6. To turn this pin as analog, you just have to write set_analog_pin(5), because this is about analog pin AN5, and not RB6.

Remember: as a consequence, these procedures don't exist when analog pins are dependent as in our first case.
CAUTION:
it's not because there are PCFG bits that PICs have dependent analog pins. Some have PCFG bits which act exactly the same as ANS bits (like some of recent 18F)
Tip: how to know if your PIC has dependent or independent pins ? First have a look at its datasheet, if you can a table like the one for 16F877, there are dependent. Also, if you configure a PIC with dependent pins as if it was one with independent pins (and vice-versa), you'll get an error. Finally, if you get an error like: "Unable to configure ADC channels. Configuration is supposed to be done using ANS bits but it seems there's no ANS bits for this PIC. Maybe your PIC isn't supported, please report !", or the like, well, this is not a normal situation, so as stated, please report !

Once configured, using ADC is easy. You'll find adc_read_high_res() and adc_read_low_res() functions, for respectively read ADC in high and low resolution. Because low resolution is coded on 8-bits, adc_read_low_res() returns a byte as the result. adc_read_high_res() returns a word.

Example with 16F877, dependent analog pins

The following examples briefly explains how to setup ADC module when analog pins are dependent from each other, using PIC 16F877.

The following diagram is here to help knowing where analog pins (blue) are and where Vref pins (red) are:

Figure 3. Analog pins and Vref pins on 16F877

Example 1: 16F877, with only one analog pin, no external voltage reference

-- beginning is about configuring the chip
-- this is the same for all examples for about 18F877
include 16f877
-- setup clock running @20MHz
pragma target OSC HS
pragma target clock 20_000_000
-- no watchdog, no LVP
pragma target WDT  disabled
pragma target LVP  enabled 

-- We'll start to set all pins as digital
-- then, using ADC, we'll configure needed
-- ones as analog.
enable_digital_io()

include print
include delay
const serial_hw_baudrate = 115_200
include serial_hardware
serial_hw_init()

-- Step 1: ADC input pin setup we wil use channel 0
pin_AN0_direction = input 
-- Step 2: Set A0 analog input and VDD as Vref
ADCON1_PCFG = 0b0000
-- Step 3: Use Frc as ADC clock 
ADCON0_ADCS = 0b11 
-- Now we can include the library
include adc
-- And initialize the whole with our parameters
adc_init()

-- will periodically send those chars
var word wmeasure
var byte bmeasure
const byte wprefix[] = "Result in high resolution: "
const byte bprefix[] = "Result in low  resolution: "

forever loop

   -- get ADC result, on channel 0
   -- this means we're currently reading on pin AN0 !

   -- access results in high resolution
   wmeasure = adc_read_high_res(0)
   -- wmeasure contains the result, as a word (byte*2)
   print_string(serial_hw_data,wprefix)
   print_word_dec(serial_hw_data,wmeasure)
   print_crlf(serial_hw_data)

   -- though we are in high resolution mode,
   -- we can still get a result as a byte, as though
   -- it were in low resolution.
   bmeasure = adc_read_low_res(0)
   print_string(serial_hw_data,bprefix)
   print_byte_dec(serial_hw_data,bmeasure)
   print_crlf(serial_hw_data)

   -- and sleep a little to prevent flooding serial...
   delay_1ms(200)
   
end loop

Example 2: 16F877, with 2 analog pins, 1 external voltage reference, that is, Vref+

This is almost the same as before, except we now want 2 (analog pins A0 and A1) + 1 (Vref+ A3), so yes we are using 3 analog pins here.

The beginning is the same, here's just the part about ADC configuration and readings. We use a for loop to go over the 2 analog pins A0 and A1:

-- Step 1: ADC input pin setup we wil use channel 0 and 1 (2 channels)
pin_AN0_direction = input 
pin_AN1_direction = input 
-- Step 2: Set A0 and A1 analog input and A3 as Vref
ADCON1_PCFG = 0b0011
-- Step 3: Use Frc as ADC clock 
ADCON0_ADCS = 0b11 
-- Now we can include the library
include adc
-- And initialize the whole with our parameters
adc_init()

-- will periodically send those chars
var word measure
var byte lowmeasure, channel
const byte prefix[] = "Channel "
const byte highstr[] = " (high) "
const byte lowstr[] = " (low) "
const byte suffix[] = ": "

forever loop

   -- loop over all channels and read
   for 2 using channel loop

  -- get ADC result, high resolution
  measure = adc_read_high_res(channel)
  -- send it back through serial
  print_string(serial_hw_data,prefix)
  print_string(serial_hw_data,highstr)
  print_byte_dec(serial_hw_data,channel)
  print_string(serial_hw_data,suffix)
  print_word_dec(serial_hw_data,measure) 
  print_crlf(serial_hw_data)
  -- and sleep a little...
  delay_1ms(100)

  -- Even if we set high resolution, we can still access results
  -- in low resolution (the 2 LSb will be removed)
  lowmeasure = adc_read_low_res(channel)
  print_string(serial_hw_data,prefix)
  print_string(serial_hw_data,lowstr)
  print_byte_dec(serial_hw_data,channel)
  print_string(serial_hw_data,suffix)
  print_byte_dec(serial_hw_data,lowmeasure) 
  print_crlf(serial_hw_data)
  -- and sleep a little...
  delay_1ms(100)

   end loop
end loop 
1 and some have 12-bits, those aren't currently handled by jallib ADC libraries, That's a restriction.