Search code examples
cembeddedpicadc

PIC16F18855 ADC registers aren't behaving the way I expect them to (C, CCS compiler)


I'm using a PIC16F18855 micro with CCS as the compiler, and I'm trying to get the ADC to work. Starting with the functions supplied by CCS, I wrote:

#device ADC=10
...
setup_adc_ports(sAN21);
setup_adc(ADC_CLOCK_DIV_8);
set_adc_channel(21);
...
fprintf(HOST, "%ld", read_adc()); //I have RS232 implemented elsewhere

This was behaving strangely, with the readings that were completely independent of the actual voltage on the pin (randomly between 9 and 18 for a 10 bit reading).

A coworker looked through the assembly that the C compiled to, and tells me that the compiler is writing to the wrong register when it tries to read the adc using the built in read_adc() function. Specifically, when it should be writing the ADGO bit to start conversion it writes to the register before ADCON0 which doesn't exist.

To work around this, I tried to implement my own functions for setting up and reading the ADC:

#byte ADC_CON_0 = getenv("SFR:ADCON0")
#byte ADC_CON_1 = getenv("SFR:ADCON1")
#byte ADC_CON_2 = getenv("SFR:ADCON2")
#byte ADC_CON_3 = getenv("SFR:ADCON3")
#byte ADC_CLK   = getenv("SFR:ADCLK")
#byte ADC_RES_H = getenv("SFR:ADRESH")
#byte ADC_RES_L = getenv("SFR:ADRESL")
#byte AN_SEL_C  = getenv("SFR:ANSELC")
#byte ADC_PCH   = getenv("SFR:ADPCH")

void adc_setup(void){
    //setting the mode and clock
    ADC_CON_0 = 0x84;   //turn on ADC and right justify it
    ADC_CON_1 = 0x00;
    ADC_CON_2 = 0x00;
    ADC_CON_3 = 0x00;
    ADC_CLK   = 0x03;   //gives Fosc/8, for 1us T_AD with 8MHz clock

    //setting the input channel and telling the pin to be analogue
    AN_SEL_C  = 0x20;   //set pin C5 to analogue input
    ADC_PCH   = 0x15;   //0x15 = 21, analogue channel 21 is pin C5
}

int16 read_adc_custom_implementation(void){
    ADC_CON_0 |= 0x01;                      //set ADGO bit to start conversion
    while(ADC_CON_0 & 0x01){}               //wait till conversion is finished (indicated by hardware reset of ADGO bit)
    return make16(ADC_RES_H, ADC_RES_L);    //read the result registers and return them combined into a 16bit integer
}

There are two major problems with my code:

If I call fprintf(HOST, "0x%x", ADC_CON_0); immediately after calling adc_setup(); I get 0x80 when I expected 0x84. That means that the 10 bit adc value is left aligned within the 2 8 bit registers, rather than right aligned. I have no idea why it doesn't write correctly. Every other register I've checked (ADCON1-3 and ADCLK) is correct.

When I call read_adc_custom_implementation(); it waits forever on the while loop, indicating that the ADGO bit is never reset as the datasheet indicates it should be.

Does anyone know why my implementations of adc_setup and read_adc_custom_implementation aren't working? Alternatively, if anyone knows why the supplied functions from CCS weren't working I'd be happy if I could use those instead.

PIC16F18855 datasheet, ADCON0 is on page 357.


Solution

  • I found a solution to the problem, with the help of a user from the CCS forums.

    The bug I was encountering is a silicon bug - if you set the ADGO bit and then read it on the next clock cycle, the ADC never reset the ADGO bit. This means my while loop will wait forever. There are more details on this bug in the errata. I had actually read this before hand, but misinterpreted it as not applying to me because what was happening with my project wasn't the same as what is described in the document.

    The workaround is to include a delay of a single clock cycle between setting the ADGO bit and checking it, ie:

    int16 read_adc_custom_implementation(void){
        ADC_CON_0 |= 0x01;                      //set ADGO bit to start conversion
        delay_cycles(1);                        //workaround
        while(ADC_CON_0 & 0x01){}               //wait till conversion is finished (indicated by hardware reset of ADGO bit)
        return make16(ADC_RES_H, ADC_RES_L);    //read the result registers and return them combined into a 16bit integer
    }
    

    This workaround is included in the read_adc() function built into CCS in versions 5.069 and later, but I was using 5.065, which was the first version of the compiler to support the 18855. I first tried the workaround and it solved one of my problems, but I was still encountering a number of other bugs. Instead of trying to work around them, I updated to 5.075 and used the built in read_adc() function. Everything started working perfectly.

    So long story short, if you have the same problem then update the compiler.