Blog Entry




PIC Analog to Digital Converter C Programming

January 18, 2009 by , under Microcontroller.




The PIC16F690 microcontroller is one of Microchip midrange 8-bit microcontroller that has a build in 10-bit resolution of Analog to Digital Converter (ADC) peripheral. The ADC is one of the important features that enable us to digitize our analog world. Usually we use the electronic sensor to convert the analog value to the voltage level value. Some of the basic sensor such as LDR (Light Dependent Resistor) is used for measuring the light intensity or NTC (Negative Temperature Coefficient) a special resistor for measuring the temperature.

Today many manufactures produce sophisticated sensors for specific task such as sharp GP2D120X is use for distance measurement, National Semiconductor LM35 for precision centigrade temperature sensor, HS12 humidity sensor from GE, TGS2442 carbon monoxide gas sensor from Figaro and many more. Therefore before we can use all of these cool sensors we have to learn the basic of how to use ADC peripheral inside the PIC 16F690 microcontroller.

On this tutorial we will learn how to program the Microchip PIC microcontroller for reading the analog signal using HITECT PICC-Lite C compiler. We will use the PICJazz 16F690 learning board (PICJazz 16F690 schema) from ermicro as our learning platform and let’s start the fun by pasting this following code to your Microchip MPLAB IDE:

// ***************************************************************************
//  File Name    : adc.c
//  Version      : 1.0
//  Description  : Analog to Digital Converter
//  Author(s)    : RWB
//  Target(s)    : PICJazz 16F690 Board
//  Compiler     : HITECT PICC-Lite Version 9.60PL1
//  IDE          : Microchip MPLAB IDE v8.00
//  Programmer   : PICKit2
//  Last Updated : 28 Dec 2008
// ***************************************************************************
#include <pic.h>
/*   PIC Configuration Bit:
**   INTIO     - Using Internal RC No Clock
**   WDTDIS    - Wacthdog Timer Disable
**   PWRTEN    - Power Up Timer Enable
**   MCLREN    - Master Clear Enable
**   UNPROTECT - Code Un-Protect
**   UNPROTECT - Data EEPROM Read Un-Protect
**   BORDIS    - Borwn Out Detect Disable
**   IESODIS   - Internal External Switch Over Mode Disable
**   FCMDIS    - Monitor Clock Fail Safe Disable
*/
__CONFIG(INTIO & WDTDIS & PWRTEN & MCLREN & UNPROTECT & UNPROTECT \
  & BORDIS & IESODIS & FCMDIS);
// Using Internal Clock of 8 Mhz
#define FOSC 8000000L
// Delay Function
#define _delay_us(x) { unsigned char us; \
	  	       us = (x)/(12000000/FOSC)|1; \
		       while(--us != 0) continue; }
void _delay_ms(unsigned int ms)
{
  unsigned char i;
  do {
    i = 4;
    do {
      _delay_us(164);
    } while(--i);
  } while(--ms);
}
void main(void)
{
  unsigned char chSign,chEye,iType;
  unsigned int iDelay;
  OSCCON=0x70;         // Select 8 Mhz internal clock
  TRISC = 0x00;        // Set All on PORTC as Output
  TRISA = 0x03;        // Input for RA0 and RA1
  ANSEL = 0b00000001;  // Set PORT AN0 to analog input AN1 to AN7 digital I/O
  ANSELH = 0;          // Set PORT AN8 to AN11 as Digital I/O
  /* Init ADC */
  ADCON0=0b10000000;   // select right justify result. ADC port channel 0
  ADCON1=0b00110000;   // Select the FRC for 8 Mhz
  ADON=1;	       // turn on the A2D conversion module
  chEye=0x01;          // Initial Eye Variables with 0000 0001
  chSign=0;
  iDelay=200;
  iType=0;
  for(;;) {
    GODONE=1;	             // initiate conversion on the channel 0
    while(GODONE) continue;  // Wait conversion done
    iDelay=ADRESL;           // Get the 8 bit LSB result
    iDelay += (ADRESH << 8); // Get the 2 bit MSB result
    // Display the LED
    if (RA1 == 0) {          // Read the Switch attached to RA1
      iType=~iType;
      chSign=0;
    }
    if (iType == 0 ) {
      if (chSign == 0) {
	PORTC=chEye;
	_delay_ms(iDelay);         // Call Delay function
	chEye=chEye << 1;
  	if (chEye > 0x04) chSign=1;
      } else {
        PORTC=chEye;
        _delay_ms(iDelay);         // Call Delay function
        chEye=chEye >> 1;
	if (chEye <= 0x01) chSign=0;
      }
    } else {
       PORTC=0x0F;
       _delay_ms(iDelay);          // Call Delay function
       PORTC=0x00;
       _delay_ms(iDelay);          // Call Delay function
    }
  }
}
/* EOF: adc.c */

The C Code

This program basically works by displaying the running LED attached to RC0, RC1, RC2 and RC3 ports on the PIC16F690 microcontroller; the speed of the running LED is controlled by the voltage value reads from the user’s trimport on the port RA0. This voltage value will be converted by the PIC ADC peripheral and passing the converted numeric value as the delay argument on the _delay_ms() function inside the loop.

The user’s trimport basically work as the voltage divider circuit and provide voltage input level to the microcontroller analog port (AN0); therefore by changing the trimmer means we change the voltage level input and this also will change the running LEDs speed.

The user’s switch is works as a toggle switch, by pressing this switch once the running LEDs will be switched to the blinking LEDs; pressing once again will switch back to the running LEDs.

For the ADC peripheral programming on the Microchip PIC16F690 microcontroller we will focus on these 2 important registers, is that all… yes you are right; again only 2 registers (if you curious of how this ADC setup being done in AVR microcontroller family, you can take a look at the similar project Analog to Digital Converter AVR C Programming posted in this blog):

1. ADCON0: A/D CONTROL REGISTER 0

The function of this register is to control the microcontroller ADC operation such as power on the ADC circuit, start converting, channel selection, ADC voltage reference selection and ADC result format presentation selection.

The CHS3, CHS2, CHS1 and CHS0 bits are used to select the ADC input channel, by setting all these bits to the logical “0” means we choose the channel 0 or AN0 (PIN 19) port which connected to the user’s trimport on the PICJazz 16F690 board.

VCFG is the voltage reference bit, the voltage reference is needed by the ADC peripheral circuit to do the converting. By setting this bit to “0” means we choose the Vdd (the PIC16F690 input voltage) as the voltage reference. The 10-bit ADC result is presented in both ADRESH and ADRESL register as follow:

By setting the ADFM bit to logical “1” we use the “right justified” result. This mean the higher 2 bits value will be place in the ADRESH register and the lower 8 bits value are in the ADRESL register. By using the C left shifting operation, we could get this 10-bit value as this following code:

iDelay = ADRESL;           // Get the 8 bit LSB result
iDelay += (ADRESH << 8);   // Get the 2 bit MSB result

Powering the ADC circuit is simply turning on the ADON bit by setting it to logical “1” and to instruct the PIC microcontroller to start the conversion we just turn on the GO/DONE bit (logical “1“) and wait until this bit turn off when the PIC16F690 microcontroller ADC peripheral done with the conversion; we use the C while statement to wait the ADC conversion as this following code:

GODONE=1;	// initiate conversion on the channel 0
while(GODONE) continue;  // Wait convertion done

2. ADCON1: A/D CONTROL REGISTER 1

In order for ADC circuit inside the PIC16F690 microcontroller to work which is use the successive approximation method, it needs to be supplied with the clock for doing the conversion. This ADCON1 register is used to select the clock sources.

Because we are using the internal clock (FRC) of 8 Mhz, then we set these ADC clock selection bits to ADCS2 = 0, ADCS1 = 1 and ADCS0 = 1 as this following code:

ADCON1=0b00110000;   // Select FRC for 8 Mhz

If you are using the external clock with VDD greater than 3 volt; make sure you choose the selection bellow typical 4us (TAD time) needed by 10-bit PIC16F690 ADC peripheral to do the conversion as this following guide (for more information please refer to the PIC16F690 datasheet):

For example if you use the external clock of 4 Mhz, in order for PIC16F690 ADC peripheral to accurately converting the analog value you only have two recommended choices either choose the Fosc/8 or Fosc/16.

The PIC16F690 microcontroller ADC peripheral is also capable of generating interrupt when it finish the conversion by setting the ADC interrupt flag ADIF bit to logical “1” in PIR1 register, but for the purpose of this tutorial we will not use this facility.

The Port Initiation

One of the most important setup before we could use the ADC is to configure the PIC16F690 port as the analog input this can be done by setting the TRISA, ANSEL and ANSELH registers as the following code:

TRISC = 0x00;        // Set All on PORTC as Output
TRISA = 0x03;        // Input for RA0 and RA1
ANSEL = 0b00000001;  // Set PORT AN0 to analog input AN1 to AN7 digital I/O
ANSELH = 0;          // Set PORT AN8 to AN11 as Digital I/O

Because the PICJazz 16F690 bard use both RA0 and RA1 ports as an input ports (RA0 for user’s trimport and RA1 for user’s switch) then we turn on the tristate input gate on these ports by setting the TRISA to 0×03 and enabling the AN0 analog input selection by setting the ANSEL to 0b00000001 (0×01 hex value). For the LED’s, we just enabling all of the PORTC tristate gate ports as an output by setting the TRISC register to all zero.

Downloading the Code

After compiling and simulating your code hook up your PICKit2 programmer to the PICJazz 16F690 board ICSP port turn the PICJazz 16F690 power. From the MPLAB IDE menu select Programmer -> Select Programmer -> Pickit2 it will automatically configure the connection and display it on the PICkit2 tab Output windows:

Now you are ready to down load the code from MPLAB IDE menu select Programmer -> Program; this will down load the HEX code into the PICJazz 16F690 board:

PIC Analog to Digital Converter C Programming

Now just relax and enjoy your work by watching your PIC 16F690 ADC in action:

Bookmarks and Share


bookmark bookmark bookmark bookmark bookmark bookmark bookmark bookmark bookmark bookmark




19 Responses to “PIC Analog to Digital Converter C Programming”

28.12.09#1

Comment by newbie999.

Compiler : HITECT PICC-Lite Version 9.7

I used this to complie your codes, but failed

I wonder why

Thanks

28.12.09#2

Comment by rwb.

I’ve compiled this project (created with HITECT PICC Lite version 9.60) using the latest HITECT PICC-Lite Version 9.7 and it work find. Could you be more specific, why it fail to compile?

30.12.09#3

Comment by newbie999.

Sorry

It is ok now

I made some silly mistakes by myself

Cheers

21.07.10#4

Comment by avinash.

Hi,

Nice post. Here is one tutorial for PIC18F4520′s ADC Interface.

http://extremeelectronics.co.in/microchip-pic-tutorials/using-analog-to-digital-converter-%e2%80%93-pic-microcontroller-tutorial/

11.05.11#5

Comment by p4n4k4ts.

Hi again!
I would like to ask if there is any difference between the left justified method and the right justified method or we get the same result with both methods?
In which situations should we prefer the one from the other?
What are the advantages and disadvantages of each method?

Thank you again for your time

11.05.11#6

Comment by rwb.

It depend on your application, because the Microchip PIC16F690 microcontroller has a 10-bit ADC resolution, therefore if you only need to use 8-bit ADC resolution you use the “left justify” (ADFM=0) method, where you could have the 8-bit ADC value in ADRESH register (just ignore the last 2 bits in ADRESL register).

ADC_Result = ADRESH; // Get 8-Bit ADC Result

When you need 10-bit ADC resolution then you use the “right justify” (ADFM=1) method, where you could have the 10-bit ADC value both in ADRESH and ADRESL registers as shown in this tutorial.

ADC_Result = ADRESL; // Get the 8-bit ADC Result
ADC_Result += (ADRESH << 8); // Get the 2 bit MSB result

11.05.11#7

Comment by p4n4k4ts.

Thanks for quick reply. It was really useful!!!

Could you also please explain how the delay_us and delay_ms functions work? I can’t understand the:

us = (x)/(12000000/FOSC)|1

Thanks for your time

11.05.11#8

Comment by rwb.

The “x” is the parameter to the _delay_us(x) function definition, while the FOSC is the microcontroller frequency clock definition (#define FOSC 8000000L, the L sign will tell the compiler to treat this value as Long integer) in Hertz. The last is the C language “OR” operator (“|”). The “OR” operator is used to ensure the calculation will always return a minimum value of 1 when x = 0.

28.05.11#9

Comment by bluesky.

Hi, could you explain:

us = (x)/(12000000/FOSC)|1

why is x should be the value, 164? and need to loop 4 times in order to have 1 ms delay?

thanks :)

29.05.11#10

Comment by rwb.

The number is based on experiment using the PIC16F690 microcontrller 8 MHz internal oscillator (it’s not really accurate). You could use the Microchip MPLAB IDE Stopwatch feature to measure the time used to execute the C code. You could read more about this feature on “Introduction to Microchip PIC Assembler Language – Part 2” article.

30.05.11#11

Comment by bluesky.

Thanks for your quick response… Your blog is very helpful … Thanks again :)

09.06.11#12

Comment by Shashank.

Hi! I’m using PIC16F877A’s A/D module with the PIC CCSC compiler to implement a simple line following robot. However I’m not getting correct analog readings in the MCU, differing from the actual values(multimeter readings) to a range of about 0.2-0.3V. Please suggest me what to do. And you have put a nice blog, I really enjoy reading your posts.

09.06.11#13

Comment by rwb.

There are many factors that make your ADC result is not as expected. The most common one are the Left/Right Justified, ADC voltage reference, and ADC Clock Source (TAD).

03.03.13#14

Comment by MikeKL6M.

What the heck does for(;;) do?

Anyone have a good reference (table) for various shortcuts like this one?

Thanks…..Mike

03.03.13#15

Comment by rwb.

It’s the C language “for loop” statement without initial and exit condition, therefore it will loop forever.

04.03.13#16

Comment by MikeKL6M.

It suddenly became intuitively obvious.
Thanks for VERY quick reply! …. Mike

05.03.13#17

Comment by MikeKL6M.

I’m having a rough time getting the A/D to work. I’m trying to get a 2 digit readout from 0 to 90 for a dc input of 0 to 5v. I got the displays multiplexed and working first but now cannot get the A to D to work.

If you are inclined to comment on my code, it is here:
http://kl6m.com/adcode.c

Thanks. Mike

05.03.13#18

Comment by rwb.

The following project perhaps could give you an idea of how to handle the 7 segment LED display:

Seven Segment Display Thermometer with PIC Microcontroller

06.03.13#19

Comment by MikeKL6M.

Thanks very much for excellent examples. But it turned out that I had a hardware problem.

Leave a Comment

You must be logged in to post a comment.