Switching ADC Channels on a PIC24F16KM202

Switching ADC Channels on a PIC24F16KM202

Postby KTrenholm » Tue Nov 20, 2018 11:02 pm

So I went to make this post on the official MChip forums, but the firewall is so busted it's nearly impossible and I had to mangle it to even get it posted there. Luckily this place doesn't have this problem.

I'm having a problem with the ADC on a PIC24F16KM202 I'm trying to trace down, using XC16 v1.31.


This firmware is reading from two analog channels. The main channel is a brightness control pot giving me an analog voltage on AN19 that I am converting to an LED brightness. The other is a thermal sensor (a MCP9700) on AN9.

Most of the time AN19 is the channel being read, I'm updating my temperature every second, switching the analog channel over to AN9 when I do so, and switching back to AN19 on the next read.


Here's the function I'm using for the switching and the read:

Code: Select all
/* FUNCTION: Read_ADC
 * PURPOSE: - Reads ADC on selected channel 16 times and returns average
 * ARGS: - uint8_t channel - Number of the ADC channel to be read
 * RETURNS: - uint16_t 10-Bit average of 16 ADC samples
 */
uint16_t Read_ADC(uint8_t channel){
    uint32_t adc_val = 0;
    uint16_t reading = 0;
    uint8_t count = 0;
    static uint8_t prev_ch = 0;
    volatile uint16_t *adc_ptr;
    AD1CHSbits.CH0NA = 0b000;
    switch (channel){
        case TEMP_ADC_CH:
            AD1CHSbits.CH0SA = 0b01001; //AN9
            break;

        case BRT_CTRL_ADC_CH:
            AD1CHSbits.CH0SA = 0b10011; //AN19
            break;
           
        default:
            return 0;
            break;
           
    }
    if (channel != prev_ch){
        __delay_us(72); /*Analog input change requires a delay*/
        prev_ch = channel;
    }
    adc_ptr = &ADC1BUF0;
    IFS0bits.AD1IF = 0;
    AD1CON1bits.ASAM = 1;
    while (!IFS0bits.AD1IF){}; /*ADC interrupts after 16 readings (set by AD1CON2bits.SMPI)*/
    AD1CON1bits.ASAM = 0;
    for (count = 0; count < 16; count++){
        reading = *adc_ptr;
        adc_val = adc_val + *adc_ptr++;
    }
    adc_val = adc_val >> 4; //Right shift 4 bits to divide by 16
    return (uint16_t) adc_val;
}



What I'm finding is that I am often (maybe 1/3 of the time? Sometimes more) getting some kind of effect between the channels. I confirmed this by printing out my ADC average whenever I call this function to read the thermal sensor.

Code: Select all
/* FUNCTION: Get_Analog Temperature
 * PURPOSE : Gets a temperature reading from analog thermal sensor
 * ARGS : uint8_t channel -> Analog channel of sensor
 * RETURNS : int16_t temperature -> Temperature in Degrees C
 *****************************************************************/
float Get_Analog_Temperature(uint8_t channel){
    uint16_t adc_raw;
    float voltage;
    float temperature;
    adc_raw = Read_ADC(channel);
    voltage = adc_raw * ADC_V_PER_COUNT;
    temperature = ((voltage - MCP9700_OFFSET)*MCP9700_T_COEFF);
    sprintf(buffer,"R:%d\r\n",adc_raw);
    UART_Xmit(buffer);
    return temperature;
}



The result of the UART transmission of this function is below:

R:414
R:414
R:452
R:414
R:414
R:452
R:414
R:452
R:452
R:414
R:414
R:414
R:452

414 is the correct reading for this channel for the voltage I am giving. 452 are the "bad" readings I'm getting that are throwing my averaging off. In this case this takes me from reading about 84C to 96C. I've noticed if I turn down the voltage coming in from the brightness pot to minimum (0V), these values swing to the other direction:

R:414
R:388
R:414
R:414
R:388
R:414
R:388
R:414
R:414
R:414
R:388
R:414
R:388

I've confirmed via multimeter and scope that the voltage on the AN9 pin is NOT changing, so it seems it must be internal to the PIC and is probably my configuration (I just can't seem to determine where). My ADC Init function is shown below:

Code: Select all
void Init_ADC(void){

    AD1CON1bits.ADSIDL = 1; /*Stop operation when idle*/
    AD1CON1bits.MODE12 = 0; /*10-Bit operation*/
    AD1CON1bits.FORM = 0b00; /*Result is unsigned integer*/
    AD1CON1bits.SSRC = 0b0111; /*Auto-Clear the SAMP bit after sampling*/
    AD1CON1bits.ASAM = 0; /*Sample Begins when SAMP is set*/

    AD1CON2bits.PVCFG = 0b00; /*Positive reference voltage is VDD pin*/
    AD1CON2bits.NVCFG0 = 0; /*Negative reference voltage is AVss*/
    AD1CON2bits.BUFREGEN = 0; /*ADC Result buffer is a FIFO starting at AD1BUF0*/
    AD1CON2bits.BUFM = 0; /*Fill buffer sequential starting at AD1BUF0*/
    AD1CON2bits.SMPI = 0b10000; /*Interrupt on 16 samples*/

    AD1CON3bits.ADRC = 0; /*ADC Clock derived from system clock FCY = 8MHz*/
    AD1CON3bits.SAMC = 0b01000; /*Auto Sample time is (8 * TAD) = 16uS*/
    AD1CON3bits.ADCS = 0b00010000; /*Conversion clock is (16 * TCY) = 2uS = TAD*/
    IFS0bits.AD1IF = 0; /*Clear the ADC Interrupt flag*/

    IEC0bits.AD1IE = 1;
    AD1CON1bits.ADON = 1; /*ADC On*/
}



Anyone out there have suggestions for what I can try (or what I'm doing wrong) to get these two channels switching and reading properly?

Thanks in advance!
User avatar
KTrenholm
 
Posts: 26
Joined: Mon Sep 10, 2018 3:57 pm
PIC experience: Professional 5+ years with MCHP products

Re: Switching ADC Channels on a PIC24F16KM202

Postby KTrenholm » Wed Nov 21, 2018 7:03 pm

Some updates as I've been messing around with this:
- I tried increasing the delay after switching channels before taking my reads to a wildly excessive 15ms with no change in behavior.
- I confirmed that I am not seeing the same issue on the brightness channel. Voltage on the temperature sensor channel has no effect on the brightness channel.
- I tried drastically increasing acquisition time to ensure the voltage was settling to the new level. 250us of acquisition time with no change in behavior.

I also have managed a "Fix" of sorts, but I don't love it.
Code: Select all
case TEMP_ADC_CH:
            AD1CON1bits.ADON = 0;
            __delay_us(10);
            AD1CON1bits.ADON = 1;
            AD1CHSbits.CH0SA = 0b01001; //AN9
            break;


Turning OFF then back ON the ADC cleans this up. Not the prettiest thing in the world but it works I guess? I'd love some other suggestions of things to try.
User avatar
KTrenholm
 
Posts: 26
Joined: Mon Sep 10, 2018 3:57 pm
PIC experience: Professional 5+ years with MCHP products

Re: Switching ADC Channels on a PIC24F16KM202

Postby KTrenholm » Mon Nov 26, 2018 8:48 pm

Figure I should keep updating this just in case someone finds it with a similar problem.

Basically what is happening, is that when I perform the 16 reads of the ADC and it dumps the results into ADC1BUF0 - ADC1BUF0, in the time between me polling the interrupt and clearing ASAM the ADC was taking another reading. Since the interrupt had already occurred, it was putting the ADC Result Buffer Pointer back to ADC1BUF0 and dropping the value there. This leaves the pointer looking at ADC1BUF1. When I switch channels and take my 16 reads, it starts at ADC1BUF1, leaving a value from the previous channel's readings in the first slot of the buffer. This is what was throwing my average off.

As I noted in my last post, turning the ADC module OFF (via ADON bit) then back on clears this up, because restarting the module points the ADC Result Buffer Pointer back to the first location: ADC1BUF0. I haven't found a way other than this to reassign the Result Buffer Pointer (other than incrementing it via taking readings).

So for now here's what I'm going to do, I'll start the ADC module OFF, turn it on when I take my readings, and turn it back off after I read them from the buffer. This will ensure that I've always got the values I want in the place I want each time through.

Code: Select all
uint16_t Read_ADC(uint8_t channel){
    uint32_t adc_val = 0;
    uint8_t count = 0;
    static uint8_t prev_ch = 0;
    volatile uint16_t *adc_ptr;
    AD1CON1bits.ADON = 1;
    AD1CHSbits.CH0NA = 0b000;
    switch (channel){
        case TEMP_ADC_CH:
            AD1CHSbits.CH0SA = 0b01001; //AN9
            break;

        case BRT_CTRL_ADC_CH:
            AD1CHSbits.CH0SA = 0b10011; //AN19
            break;
           
        default:
            return 0;
            break;
           
    }
    if (channel != prev_ch){
        __delay_us(100);             /*Analog input change requires a delay*/
        prev_ch = channel;
    }
    adc_ptr = &ADC1BUF0;
    IFS0bits.AD1IF = 0;
    AD1CON1bits.ASAM = 1;
    while (!IFS0bits.AD1IF){};  /*ADC interrupts after 16 readings (set by AD1CON2bits.SMPI)*/
    AD1CON1bits.ASAM = 0;
    //Turning off ADC Resets result buffer pointer to ADC1BUF0
    AD1CON1bits.ADON = 0;   
    for (count = 0; count < 16; count++){
        adc_val = adc_val + *adc_ptr++;
    }
    adc_val = adc_val >> 4; //Right shift 4 bits to divide by 16
   
    return (uint16_t) adc_val;
}


Of course, credit where credit is due to the folks at the official forum for finding and pointing this out HERE
User avatar
KTrenholm
 
Posts: 26
Joined: Mon Sep 10, 2018 3:57 pm
PIC experience: Professional 5+ years with MCHP products


Return to ADC & Comparators

Who is online

Users browsing this forum: No registered users and 3 guests

cron