Programming PIC 18 using XC8 (MPLAB X) : ADC (using adc.h)

This will be the last of the XC8 series for PIC18. Next will be bunch of projects (hopefully I’ll get the time). ADC is the last topic. There are too many stuffs, but the point is read the datasheet. Datasheet tells you everything and my previous posts will give you an idea of linking datasheet with peripheral library and use in your application.

ADC in PIC18’s are of 10 bit resolution. Way too good for most of our needs. Before complaining about ADC not working, are  you sure you read every word of the datasheet and understood it? ADC’s have a lot of configuration. Going for high sampling rate is not always good. You got to set the acquisition time for your ADC. Lot of people make mistake with the settings and sampling rate of the ADC. For my tutorial I’ll be using a potentiometer. Not a big deal with the sampling rate. But still will give you an idea of setting the interrupts and other features.

Read this application note that explains ADC for pic midrange in detail http://ww1.microchip.com/downloads/en/DeviceDoc/31021a.pdf

In order to set the ADC, these are the following steps

  • Set ADC clock
  • Set conversion time (TAD) – applicable if you see ADCON2 in the datasheet
  • Justification (right of left – padding zeros)
  • +Ve reference – can be either Vdd or voltage at pin AN3 (analog channel 3’s pin)
  • -Ve reference – can be either Vss or voltage at pin AN2 (analog channel 2’s pin)
  • Interrupt ON
    Note : All the above list may not be applicable to all controllers. Please read the datasheet.

In lot of places you would have seen polling method. So I wont be showing that. In our method the ADC will trigger an interrupt after conversion is complete.

According to peripheral library these are the functions we will be using

void OpenADC( unsigned char config, unsigned char config2, unsigned char portconfig)

How did I know it takes 3 arguments? according to library there are also OpenADC function with only 2 arguments? Well, read the datasheet and select your OpenADC with the available registers.

Here is my setting for OpenADC()

OpenADC(ADC_FOSC_2 & ADC_RIGHT_JUST & ADC_20_TAD, ADC_CH0 & ADC_INT_ON & ADC_VREFPLUS_VDD & ADC_VREFMINUS_VSS, ADC_0ANA);

My input is a potentiometer, so not a big deal with the settings. Select your TAD according to your requirement.

You have this awesome interrupt enabler that also setup the interrupt. Here is the list of registers affected

Untitled

In order to setup the interrupt here is an awesome macro given in adc.h

#define ADC_INT_ENABLE() (PIR1bits.ADIF=0,INTCONbits.PEIE=1,PIE1bits.ADIE=1)

Here’s the complete ADC code

/* * File: main.c * Author: Singular Engineer * * Created on June 1, 2013, 12:10 AM */ #define _XTAL_FREQ 8000000 //The speed of your internal(or)external oscillator #define USE_AND_MASKS #include <xc.h> #include <stdlib.h> #include "config.h" #include <plib/usart.h> #include <plib/adc.h> unsigned char UART1Config = 0, baud = 0; unsigned char MsgFromPIC[] = "\r\nADC value is :"; int ADCValue = 0; unsigned char ADCStringVal[4]; void SetupClock(void); void main(int argc, char** argv) { SetupClock(); // Internal Clock to 8MHz TRISAbits.RA0 = 1; TRISCbits.RC6 = 0; //TX pin set as output TRISCbits.RC7 = 1; //RX pin set as input //Configure UART UART1Config = USART_TX_INT_OFF & USART_RX_INT_ON & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_BRGH_HIGH ; baud = 51; OpenUSART(UART1Config,baud); //Configure ADC OpenADC(ADC_FOSC_2 & ADC_RIGHT_JUST & ADC_20_TAD, ADC_CH0 & ADC_INT_ON & ADC_VREFPLUS_VDD & ADC_VREFMINUS_VSS, ADC_0ANA); ADC_INT_ENABLE(); //Easy ADC interrupt setup ei(); //remember the master switch for interrupt? //Start ADC ConvertADC(); while(1) //infinite loop { } } void SetupClock() { OSCCONbits.IRCF0 = 1; OSCCONbits.IRCF1 = 1; OSCCONbits.IRCF2 = 1; } void interrupt ADCInterrupt() { //check if the interrupt is caused by ADC if(PIR1bits.ADIF == 1) { ADCValue = ReadADC(); putsUSART(MsgFromPIC); putsUSART(itoa( ADCStringVal, ADCValue,10)); //convert to string //Reset interrupt flag and start conversion again ADIF = 0; ConvertADC(); } }

And the output looks like

image

image

Author: singularengineer

31 thoughts on “Programming PIC 18 using XC8 (MPLAB X) : ADC (using adc.h)

  1. Thank you so much for these tutorials! Given that Microchip haven’t published a whole lot of tutorials or guides on the subject, or 3rd party books on XC8, your small tutorials on each section have helped me greatly so far!

  2. I stumbled into this site and found your clear explanations and well-documented code. I’m just making the transition to XC8 and this was very helpful. Thanks.

  3. Hi and thank you again for this great tutorial. I’m having some problems with ADC. I would like to test this module by doing a simple led barmeter that “fills” 4 led according to ADC’s read value. But with this code all leds are always on (probably it’s reading 5V). the analogic source is a potentiometer that brings a variable voltage (in range of 0 to 5V, checked with multimeter) on RA4/AN3 pin of PIC18FK22. on RC0-3 pins there are 4 leds and ADC input that connected to the potentiometer is RA4/AN3 pin. Here is my code, without the #include that are always the same (xc.h,plib/adc.h, configuration header):
    ….(some other rows)
    #define USE_OR_MASKS
    void setupClock(void);
    ….(some other rows)

    int ADCval=0;

    void main(void){
    setupClock();
    TRISAbits.RA4=1;
    ANSELbits.ANS3=1;
    OpenADC(ADC_FOSC_2|ADC_RIGHT_JUST|ADC_12_TAD,ADC_CH3|ADC_INT_ON,ADC_REF_VDD_VSS,ADC_3ANA);
    TRISC=0b00000000;

    LATC0=0;
    LATC1=0;
    LATC2=0;
    LATC3=0;
    ADC_INT_ENABLE();
    ei();
    ConvertADC();

    while(1) //infinite loop
    {}
    void setupClock()
    {
    OSCCONbits.IRCF0=1;
    OSCCONbits.IRCF1=1;
    OSCCONbits.IRCF2=1;
    }

    void interrupt ADCInterrupt()
    {
    if(PIR1bits.ADIF==1)
    {
    ADCval=ReadADC();
    if(ADCval=256&&ADCval=512&&ADCval=768)
    {
    LATC0=1;
    LATC1=1;
    LATC2=1;
    LATC3=1;
    }
    ADIF=0;
    ConvertADC();
    }
    }

    I also tryed to change the line OpenADC(…) with direct register bits configuration like this

    ADCON0bits.ADON=1;
    ADCON0bits.CHS0=1;
    ADCON0bits.CHS1=1;
    ADCON0bits.CHS2=0;
    ADCON0bits.CHS3=0;
    ADCON1bits.PVCFG0=0;
    ADCON1bits.PVCFG1=0;
    ADCON1bits.NVCFG0=0;
    ADCON1bits.NVCFG1=0;
    ADCON2bits.ADFM=1;
    ADCON2bits.ACQT0=1;
    ADCON2bits.ACQT1=0;
    ADCON2bits.ACQT2=1;
    ADCON2bits.ADCS0=0;
    ADCON2bits.ADCS1=0;
    ADCON2bits.ADCS2=0;

    and there’s a change, but it’s only that now only the first led (RC0 pin) is on (like adc is reading a value less than 256.
    I’m a little bit perplexed, because on the peripherial library, looking to my pic adc’s table, at OpenADC function there are some things that I’m not understanding, for example why ADC’s channels starts from CH3 and goes to CH11? Datasheet says that there are AN0-11! Am I missing something, like using wrong channel or settings? thank you again!

    1. There are some things to be noted in your code. Check your if conditions. You are using ‘=’ instead of ‘==’. ADC is 10 bit resolution. I dont know what you are doing with 4 bits/LED’s. Please use microchip forums for a quick reply (and may be even debug your code).

  4. Hi, I’m sorry, there’s a big mistake happened in copy/paste. I don’t know why but the “if” in the interrupt is not what I have in the source. there are 4 if clauses, first check if ADCval (that has the ReadADC return value) is less than 256 (in this case only RC0 is equal to 1), second check if ADCval is bigger or equal 256 and less than 512 (in this case RC0 and RC1 are equal to 1) and so on. what I would like to get is a ledbar that “shows” the value of ReadADC function (eg. if four leds are on it means that ReadADC value is bigger than 768). basically I divided the ADC interval (0 to 1023) in 4 intervals and RC0-3 indicates in which interval is the reading. I tryed to substitute the line: ADCval=ReadADC(); with ADCval=500 (I tryed many numbers to test all intervals) and it works good so the program effectively enters the interrupt routine and if clauses are correct. So the problem is “only” in the ReadADC() return value (I don’t mean that the problem is on the function but I think it’s in the settings). But RA4 pin is set to input and ANS3 is set to analogic, ADC channel is set to number 3 (I don’t know why in peripherial library it says that my PIC has only CH3 to CH11 but in datasheet there are also CH0-2!). Is the same if I use OpenADC function or the setting for each register, like in the bottom of my previous post? thank you again, now I try on microchip forum

  5. Hi, I’m sorry to bother you again, unfortunely Microchip forum is temporary off-service (it’s all against me 🙁 ) so I’m asking your help another time. I debugged it, nothing wrong but like as I expected ADRES registers (high and low) values remain to 0x00 so the return value of ReadADC() is always 0. I continue to think that my ADC configuration (either by using OpenADC(ADC_FOSC_2|ADC_RIGHT_JUST|ADC_12_TAD,ADC_CH3|ADC_INT_ON,ADC_3ANA) function or by setting all registers associated with ADC like I wrote near the bottom of my first comment here), but I really can’t understand where! I’m using RA4/AN3 pin of my PIC for analog input so it should be correct to use ADC_CH3 and ADC_3ANA, anywhere I tryed to use all channels of ADC (setting ADC_13ANA to make all channel correlated to ADC as analog input) and trying all channel as input for ADC (ADC_CH3, ADC_CH4 and so on till end) but nothing changed. Don’t consider the interrupt routine, because the one written in my previous comment is wrong but on source code is correct (like I said, I tested it just substituting the ReadADC() with a number and it works good)

  6. Hi Singular,
    Great post, I only have one question. About 1/5 in your post you mention about “peripheral library” and the function OpenADC. How do I know which OpenADC function to select and its configuration by reading the datasheet.
    From the data sheet I get what would be required to do just by setting the ADCON registers but not sure how to translate different PIC to different OpenADC.

    Just an example of this is PIC18f4520 & PIC18f46k22 have different ADCON registers (mostly ADCON1 is different), would I configure OpenADC the same?

    Thanks for your time

    Regards,

  7. @Carl Hi! Normally it’s in C:\Program Files\Microchip\xc8\v1.30\docs or something like. It’s a pdf with all (or almost all) supported chips. Look in that folder and if you don’t find it try to google it! Sorry for approximative informations but I dont have my pc now!

  8. I got some updates on my problem. I’m going crazy on this but I’ll come out! I’m debugging it with many changes and tryes… I noticed that OpenADC(…) is not configuring it as I would but if I set all registers associated with ADC (ADCON0, ADCON1, ADCON2, ANSEL) it “works”: I put the double quotes on works because it works only if I toggle a breakpoint in debugging mode, but in normal funciton it doesn’t works! It seems that ConvertADC() doesn’t restart conversion!
    I try to reinsert the source code:
    int ADCval=0;

    void main(void)
    {
    setupClock();
    TRISAbits.RA4=1;
    ANSELbits.ANS3=1;
    ADCON0bits.ADON=1;
    ADCON0bits.CHS=0b0011;
    ADCON1bits.PVCFG=0b00;
    ADCON1bits.NVCFG=0b00;
    ADCON2bits.ADFM=1;
    ADCON2bits.ACQT=0b111;
    ADCON2bits.ADCS=0b000;
    TRISC=0b00000000;
    LATC0=0;
    LATC1=0;
    LATC2=0;
    LATC3=0;
    ADC_INT_ENABLE();
    ei();
    ConvertADC();

    while(1) //infinite loop
    {
    }

    }

    void setupClock()
    {
    OSCCONbits.IRCF0=1;
    OSCCONbits.IRCF1=1;
    OSCCONbits.IRCF2=1;
    }

    void interrupt ADCInterrupt()
    {
    if(PIR1bits.ADIF==1)
    {
    ADCval=ReadADC();
    if(ADCval=256&&ADCval=512&&ADCval=768)
    {
    LATC0=1;
    LATC1=1;
    LATC2=1;
    LATC3=1;
    }
    PIR1bits.ADIF=0;
    ConvertADC();
    }
    }
    here we are. If I launch it in debugging mode and toggle a breakpoint in line
    if(ADCval<256)
    then I see ADCval and ADRES(high and low) that changes values from time to time but if I load the project on MCU it doesn't do nothing! Anyone got any idea on why this could happen? Because I don't. Anyway if you can help me, I will be really gratefull to you!

  9. another time something happens on double ‘&’ in if clauses. I rewrite it, using AND instead of double ‘&’

    if(ADCval=256 AND ADCval=512 AND ADCval=768)
    {
    LATC0=1;
    LATC1=1;
    LATC2=1;
    LATC3=1;
    }
    PIR1bits.ADIF=0;
    ConvertADC();
    }

  10. god! It won’t be never correct! I try to write it litteraly hoping that now it will be displayed correctly:
    if ADCval is less than 256 then only RC0=1 and RC1-3=0
    if ADCval is between 256 and 512 then RC0=1, RC1=1 and RC2-3=0
    if ADCval is between 512 and 768 then RC0-2=1 and RC3=0
    if ADCval is greater than 768 then all RC0-3=1
    syntax is correct and the problem is not in if clauses. I’m really sorry for this multiposts. Thank you again for put up with me!

  11. @Alessandro
    Try this, very easy, no interrupts, try to get your ADC to work first, I haven’t tried this with the PIC18F46K22 yet. Hope this helps.

    #include
    #include
    #include
    #include
    #include
    #pragma config WDTE = OFF

    #define _XTAL_FREQ 8000000 //The speed of your internal(or)external oscillator

    int ADCValue = 0;
    void SetupClock(void);

    int main(int argc, char** argv) {

    SetupClock(); // Internal Clock to 8MHz

    while(1) //infinite loop
    {
    //Start ADC
    OpenADC(ADC_FOSC_16 & ADC_RIGHT_JUST & ADC_12_TAD, ADC_CH3 & ADC_INT_OFF
    & ADC_VREFPLUS_VDD & ADC_VREFMINUS_VSS,11); //(config, config2, portconfig)
    ConvertADC(); // start conversion
    while(BusyADC()){} // wait while converting
    ADCValue = ReadADC(); //converted

    }

    }
    void SetupClock()
    {
    OSCCON=0x70;
    }

  12. @Alessandro
    Try this after you proven your ADC works
    if (ADCval<=256) // sets condition to unlock
    LATC0=1;
    else if (ADCval<=512)
    LATC0=1;
    LATC1=1;
    else if (ADCval<=768)
    LATC0=1;
    LATC1=1;
    LATC2=1;
    else
    LATC0=1;
    LATC1=1;
    LATC2=1;
    LATC3=1;

  13. Here I am. I’m sorry but I don’t see any code button, on comment box there are only Name, Email and Website… Anywhere, I tryed carl’s code, but even without using interrupt I got the same problem. But while I was debugging I noticed something: in adcread.c first two #include (pic18cxxx.h and adc.h) give a “cannot find include file” error and ReadADC function is “commented” (I mean written in light grey like comments)! why it happens? so I tryed to remake functions of adc.h and unuse all function but there are no changes, program works only in debugging mode (with something to note, like ADCval value says 650 even if potentiometer is at maximum and voltage on RA4/AN3 pin is 4.5V, but 650 means a voltage of 3.17V) here is the code, in final try so without using adc.h functions (but nothing change if I use them)
    [code]
    #include
    #include
    #include “config.h”
    #define _XTAL_FREQ 16000000 //16MHz
    #include “myDelays.h”
    #define USE_OR_MASKS

    void setupClock(void);
    unsigned int ADCread(void);

    int ADCval=0;

    void main(void){
    setupClock();
    TRISAbits.RA4=1;
    ANSELbits.ANS3=1;
    ADCON0bits.ADON=1;
    ADCON0bits.CHS=0b0011;
    ADCON1bits.PVCFG=0b00;
    ADCON1bits.NVCFG=0b00;
    ADCON2bits.ADFM=1;
    ADCON2bits.ACQT=0b111;
    ADCON2bits.ADCS=0b000;
    PIE1bits.ADIE=0;
    TRISC=0b00000000;
    LATC0=0;
    LATC1=0;
    LATC2=0;
    LATC3=0;

    while(1) //infinite loop
    {
    ADCON0bits.GO=1;
    while(ADCON0bits.GO==1)
    {
    //do nothing
    }
    ADCval=ADCread();
    if(ADCval=256&&ADCval=512&&ADCval=768)
    {
    LATC0=1;
    LATC1=1;
    LATC2=1;
    LATC3=1;
    }
    }

    }

    void setupClock()
    {
    OSCCONbits.IRCF0=1;
    OSCCONbits.IRCF1=1;
    OSCCONbits.IRCF2=1;
    }

    unsigned int ADCread(void)
    {
    return (((unsigned int)ADRESH)<<8)|(ADRESL);
    }
    [/code]
    I'm really going crazy….

  14. Thank you anywhere, I’ll try to change PIC and we will see… the thing that drives me crazy is that in debugging mode it works! It’s like when you hear a noise from your car and when you go to the mechanic to check it the noise stops! If someone has any kind of help I’ll be very grateful to him! Thanks for all and have a nice weekend!

  15. I got it guys!! Problem was in configuration bits, where I wasn’t looking for! For give it another try I maked another project, while I was configuring configuration bits I noticed that previous configuration was setting internal RC oscillator AND OSCOUT ON RA4 PIN!!! so that was the problem! ADC’s pin was “shared” with oscillator out!! And that’s why in debugging mode ADC was reading some strange values!! probably, stopping it with the breakpoint gave ADC the time to convert the value of oscillator out voltage, but in normal working it was too much fast to convert it! Now it works great, next step is to try if OpenADC(…) works, because I’m still using registers direct configuration, but I think that now also that will work! Thanks again for these useful tutorials, and for bear with me 🙂

  16. I would like to add a thing, for next people who will have problems like me! There’s something strange in peripherial library of PIC18F14K22: in OpenADC(…) function explanation, for config3 section, it seems that ADC_REF_VDD_VSS will set Verf+=Vdd and Vref-=Vss. That’s not true! To set it to use Vdd as Vref+ and Vss as Vref- the correct configuration is, for config3 (so the third argoument of OpenADC function, using and masks) ADC_REF_VDD_VDD&ADC_REF_VDD_VSS
    Take a look on indicated page of peripherial library to understand why I think that is misleading! It seems (to me) that config3 has only one keyword but in fact they are two. I understood it looking at adc.h

  17. According to PIC18F45K50 datasheet table 29-23 TAD is 1uS and aquisition time is 1.4uS which is the minimum values. So if we are running at 8Mhz FOSC then minimum of ADC_FOSC_8 for adc clock and ADC_2_TAD can be used. Is it correct configuration or am I making any mistake here?

  18. Hi, I do not know if you still check the comments on this website, but I thought I would ask my question. I would like to read multiple channels of the ADC at 1kHz or so. I was attempting to read the ADC with a timer0 interrupt running at 1kHz, but that does not seem to work. I would use the ADC interrupt, but I could not easily configure it to read in the frequency I required. Do you have any suggestions? I can post my code if necessary.

    1. Hi Alex,
      Sorry for the delayed response. Yes, I still check the comments. How many channels do you like to read? What is the maximum ADC speed? and What is the Tad (time to acquire) per channel? Also, how many ADC’s do you have in the controller you are using? If possible try to use all ADC’s to effectively give enough time per channel to acquire your signal properly.

      I would highly recommend you using the microchip’s forum since they are very quick to help you. I wont be active until April.

      Thank You.

  19. @singularengineer
    Thanks for replying! Yes, that is precisely what I did and I have fixed the problem. I am now trying to configure UART to work with my project. Can you perhaps provide information on the serial monitor you are using? Maybe provide a link to it? I am currently using the arduino integrated monitor, but I am getting strange ASCII results.

    Thanks!

  20. Alex :@singularengineer Thanks for replying! Yes, that is precisely what I did and I have fixed the problem. I am now trying to configure UART to work with my project. Can you perhaps provide information on the serial monitor you are using? Maybe provide a link to it? I am currently using the arduino integrated monitor, but I am getting strange ASCII results.
    Thanks!

    Increasing the baud rate to 19200 on the serial monitor fixed the result to what I wanted. However, I do not understand as in the code it is set to 9600… Any idea why?

  21. Alex :@singularengineer Thanks for replying! Yes, that is precisely what I did and I have fixed the problem. I am now trying to configure UART to work with my project. Can you perhaps provide information on the serial monitor you are using? Maybe provide a link to it? I am currently using the arduino integrated monitor, but I am getting strange ASCII results.
    Thanks!

    So, it turns out I needed to increase the baud rate on the serial monitor to 19200 so that I didn’t get garbled data (I do not know why since the baud rate is set to 9600 in the code). I have also transferred to PuTTY instead of arduino. Now, I am trying to user your Rx interrupt code that you posted in your UART tutorial. While testing it, I found that when I type something in and press enter, the PIC does not seem to transmit anything back to the PC as the PC monitor does not echo what I typed. Can you provide any help or an explanation? Thanks.
    P.S. I know that this is in the wrong section of your website, but since I already made a post here, I might as well continue… sorry for any inconveniences.

    1. For most PIC18’s the UART RX pin is shared with ADC. So you may have to make your RX it into a Digital I/O. Use ANSELC = 0x00; to convert all pins in PORTC to digital. Also try one character echo and then move your way up. Remember ANSELC=0x00; will make all pins in port C to be I/O. If you had and ADC working on port C, they will stop working after its made into digital.

  22. @singularengineer
    Thanks for the quick reply! However, I am using the PIC18F23K20, and I do not believe that the Rx pin is shared with the ADC. However, I still set the ANSEL equal to 0x00 (ANSELC is not predefined in the xc8 compiler), and I was still unable to see a change on PuTTY.

    1. You are right.. Its not been shared.
      Try working with one character echo. Then move your way up. Also try to use microchip forums if you can. They are pretty quick. But you might have to post your code in there if you need a better reply.

Leave a Reply

Your email address will not be published. Required fields are marked *